Skip to content

Commit 45d606d

Browse files
committed
Add processing_core crate and webcam support
1 parent 96ca163 commit 45d606d

File tree

29 files changed

+1092
-384
lines changed

29 files changed

+1092
-384
lines changed

Cargo.lock

Lines changed: 526 additions & 127 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ workspace = true
1111
default = ["wayland"]
1212
wayland = ["processing_render/wayland"]
1313
x11 = ["processing_render/x11"]
14+
webcam = ["dep:processing_webcam"]
1415

1516
[workspace]
1617
resolver = "3"
@@ -23,14 +24,25 @@ too_many_arguments = "allow"
2324
[workspace.dependencies]
2425
bevy = { git = "https://github.com/bevyengine/bevy", branch = "main" }
2526
processing = { path = "." }
27+
processing_core = { path = "crates/processing_core" }
2628
processing_pyo3 = { path = "crates/processing_pyo3" }
2729
processing_render = { path = "crates/processing_render" }
2830
processing_midi = { path = "crates/processing_midi" }
31+
processing_webcam = { path = "crates/processing_webcam" }
2932

3033
[dependencies]
3134
bevy = { workspace = true }
35+
processing_core = { workspace = true }
3236
processing_render = { workspace = true }
3337
processing_midi = { workspace = true }
38+
processing_webcam = { workspace = true, optional = true }
39+
tracing = "0.1"
40+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
41+
42+
[target.'cfg(target_arch = "wasm32")'.dependencies]
43+
wasm-bindgen-futures = "0.4"
44+
js-sys = "0.3"
45+
web-sys = { version = "0.3", features = ["Window"] }
3446

3547
[dev-dependencies]
3648
glfw = "0.60.0"
@@ -86,6 +98,11 @@ path = "examples/midi.rs"
8698
name = "gltf_load"
8799
path = "examples/gltf_load.rs"
88100

101+
[[example]]
102+
name = "webcam"
103+
path = "examples/webcam.rs"
104+
required-features = ["webcam"]
105+
89106
[profile.wasm-release]
90107
inherits = "release"
91108
opt-level = "z"

crates/processing_core/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "processing_core"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[lints]
7+
workspace = true
8+
9+
[dependencies]
10+
bevy = { workspace = true }
11+
raw-window-handle = "0.6"
12+
thiserror = "2"
13+
tracing = "0.1"
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//! Options object for configuring various aspects of libprocessing.
2-
//!
3-
//! To add a new Config just add a new enum with associated value
4-
51
use bevy::prelude::Resource;
62
use std::collections::HashMap;
73

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ pub enum ProcessingError {
3636
UnknownMaterialProperty(String),
3737
#[error("GLTF load error: {0}")]
3838
GltfLoadError(String),
39+
#[error("Webcam not connected")]
40+
WebcamNotConnected,
3941
}

crates/processing_core/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
pub mod config;
2+
pub mod error;
3+
4+
use std::cell::RefCell;
5+
use std::sync::OnceLock;
6+
7+
use bevy::app::App;
8+
use tracing::debug;
9+
10+
static IS_INIT: OnceLock<()> = OnceLock::new();
11+
12+
thread_local! {
13+
static APP: RefCell<Option<App>> = const { RefCell::new(None) };
14+
}
15+
16+
pub fn app_mut<T>(cb: impl FnOnce(&mut App) -> error::Result<T>) -> error::Result<T> {
17+
let res = APP.with(|app_cell| {
18+
let mut app_borrow = app_cell.borrow_mut();
19+
let app = app_borrow
20+
.as_mut()
21+
.ok_or(error::ProcessingError::AppAccess)?;
22+
cb(app)
23+
})?;
24+
Ok(res)
25+
}
26+
27+
pub fn is_already_init() -> error::Result<bool> {
28+
let is_init = IS_INIT.get().is_some();
29+
let thread_has_app = APP.with(|app_cell| app_cell.borrow().is_some());
30+
if is_init && !thread_has_app {
31+
return Err(error::ProcessingError::AppAccess);
32+
}
33+
if is_init && thread_has_app {
34+
debug!("App already initialized");
35+
return Ok(true);
36+
}
37+
Ok(false)
38+
}
39+
40+
pub fn set_app(app: App) {
41+
APP.with(|app_cell| {
42+
IS_INIT.get_or_init(|| ());
43+
*app_cell.borrow_mut() = Some(app);
44+
});
45+
}
46+
47+
pub fn take_app() -> Option<App> {
48+
APP.with(|app_cell| app_cell.borrow_mut().take())
49+
}

crates/processing_pyo3/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ crate-type = ["cdylib"]
1414
default = ["wayland"]
1515
wayland = ["processing/wayland", "glfw/wayland"]
1616
x11 = ["processing/x11"]
17+
webcam = ["processing/webcam", "dep:processing_webcam"]
1718

1819
[dependencies]
1920
pyo3 = "0.27.0"
2021
processing = { workspace = true }
22+
processing_webcam = { workspace = true, optional = true }
2123
bevy = { workspace = true, features = ["file_watcher"] }
2224
glfw = { version = "0.60.0"}
2325
png = "0.18"

crates/processing_pyo3/src/graphics.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ impl Light {
5959
#[pyclass]
6060
#[derive(Debug)]
6161
pub struct Image {
62-
entity: Entity,
62+
pub(crate) entity: Entity,
63+
}
64+
65+
impl Image {
66+
pub(crate) fn from_entity(entity: Entity) -> Self {
67+
Self { entity }
68+
}
6369
}
6470

6571
impl Drop for Image {

crates/processing_pyo3/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ mod glfw;
1212
mod gltf;
1313
mod graphics;
1414
pub(crate) mod material;
15+
#[cfg(feature = "webcam")]
16+
mod webcam;
1517

1618
use graphics::{Geometry, Graphics, Image, Light, Topology, get_graphics, get_graphics_mut};
1719
use material::Material;
@@ -79,6 +81,12 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> {
7981
m.add_function(wrap_pyfunction!(emissive, m)?)?;
8082
m.add_function(wrap_pyfunction!(unlit, m)?)?;
8183

84+
#[cfg(feature = "webcam")]
85+
{
86+
m.add_class::<webcam::Webcam>()?;
87+
m.add_function(wrap_pyfunction!(create_webcam, m)?)?;
88+
}
89+
8290
Ok(())
8391
}
8492

@@ -544,3 +552,14 @@ fn emissive(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult
544552
fn unlit(module: &Bound<'_, PyModule>) -> PyResult<()> {
545553
graphics!(module).unlit()
546554
}
555+
556+
#[cfg(feature = "webcam")]
557+
#[pyfunction]
558+
#[pyo3(signature = (width=None, height=None, framerate=None))]
559+
fn create_webcam(
560+
width: Option<u32>,
561+
height: Option<u32>,
562+
framerate: Option<u32>,
563+
) -> PyResult<webcam::Webcam> {
564+
webcam::Webcam::new(width, height, framerate)
565+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use bevy::prelude::Entity;
2+
use processing_webcam::{
3+
WebcamFormat, webcam_create, webcam_create_with_format, webcam_destroy, webcam_image,
4+
webcam_is_connected, webcam_resolution,
5+
};
6+
use pyo3::{exceptions::PyRuntimeError, prelude::*};
7+
8+
use crate::graphics::Image;
9+
10+
#[pyclass(unsendable)]
11+
pub struct Webcam {
12+
entity: Entity,
13+
}
14+
15+
#[pymethods]
16+
impl Webcam {
17+
#[new]
18+
#[pyo3(signature = (width=None, height=None, framerate=None))]
19+
pub fn new(
20+
width: Option<u32>,
21+
height: Option<u32>,
22+
framerate: Option<u32>,
23+
) -> PyResult<Self> {
24+
let entity = match (width, height, framerate) {
25+
(Some(w), Some(h), Some(fps)) => webcam_create_with_format(WebcamFormat::Exact {
26+
resolution: bevy::math::UVec2::new(w, h),
27+
framerate: fps,
28+
}),
29+
(Some(w), Some(h), None) => webcam_create_with_format(WebcamFormat::Resolution(
30+
bevy::math::UVec2::new(w, h),
31+
)),
32+
(None, None, Some(fps)) => webcam_create_with_format(WebcamFormat::FrameRate(fps)),
33+
_ => webcam_create(),
34+
}
35+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
36+
37+
Ok(Self { entity })
38+
}
39+
40+
pub fn is_connected(&self) -> PyResult<bool> {
41+
webcam_is_connected(self.entity)
42+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
43+
}
44+
45+
pub fn resolution(&self) -> PyResult<(u32, u32)> {
46+
webcam_resolution(self.entity)
47+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
48+
}
49+
50+
pub fn image(&self) -> PyResult<Image> {
51+
let entity = webcam_image(self.entity)
52+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
53+
Ok(Image::from_entity(entity))
54+
}
55+
}
56+
57+
impl Drop for Webcam {
58+
fn drop(&mut self) {
59+
let _ = webcam_destroy(self.entity);
60+
}
61+
}

0 commit comments

Comments
 (0)