diff --git a/Cargo.lock b/Cargo.lock index 5e14b7b..3c3289f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -600,6 +600,7 @@ dependencies = [ "futures-lite", "futures-util", "js-sys", + "notify-debouncer-full", "ron", "serde", "stackfuture", @@ -2516,6 +2517,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -2621,6 +2631,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -3229,6 +3248,26 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "ktx2" version = "0.4.0" @@ -3506,6 +3545,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "moxcms" version = "0.7.10" @@ -3662,6 +3713,43 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.10.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "ntapi" version = "0.4.2" @@ -5612,6 +5700,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -6221,6 +6315,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -6254,13 +6357,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -6282,6 +6402,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6294,6 +6420,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6306,12 +6438,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6324,6 +6468,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6336,6 +6486,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6348,6 +6504,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6360,6 +6522,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winit" version = "0.30.12" diff --git a/Cargo.toml b/Cargo.toml index bf30fe3..48019c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ type_complexity = "allow" too_many_arguments = "allow" [workspace.dependencies] -bevy = { git = "https://github.com/bevyengine/bevy", branch = "main" } +bevy = { git = "https://github.com/bevyengine/bevy", branch = "main", features = ["file_watcher"] } processing = { path = "." } processing_pyo3 = { path = "crates/processing_pyo3" } processing_render = { path = "crates/processing_render" } diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 5f7be05..4c5812a 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -61,6 +61,11 @@ impl Topology { } } +#[pyclass] +pub struct Sketch { + pub source: String, +} + #[pymethods] impl Geometry { #[new] @@ -121,6 +126,11 @@ impl Graphics { let mut config = Config::new(); config.set(ConfigKey::AssetRootPath, asset_path.to_string()); + config.set( + // TODO: this needs to be handed to us by python + ConfigKey::SketchRootPath, + "/home/moon/Code/libprocessing/crates/processing_pyo3/examples".to_string(), + ); init(config).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let surface = glfw_ctx @@ -141,6 +151,21 @@ impl Graphics { }) } + pub fn poll_for_sketch_update(&self) -> PyResult { + match poll_for_sketch_updates().map_err(|_| PyRuntimeError::new_err("SKETCH UPDATE ERR"))? { + Some(sketch) => Ok(Sketch { + source: sketch.source, + }), + None => Ok(Sketch { + source: "".to_string(), + }), + } + + // Ok(Sketch { + // source: sketch.unwrap().source, + // }) + } + pub fn background(&self, args: Vec) -> PyResult<()> { let (r, g, b, a) = parse_color(&args)?; let color = bevy::color::Color::srgba(r, g, b, a); diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 3985d24..fb088fd 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -12,7 +12,13 @@ mod glfw; mod graphics; use graphics::{Geometry, Graphics, Image, Topology, get_graphics, get_graphics_mut}; -use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyTuple}; +use pyo3::{ + exceptions::PyRuntimeError, + ffi::c_str, + prelude::*, + types::{PyDict, PyTuple}, +}; +use std::ffi::{CStr, CString}; use std::env; @@ -81,8 +87,8 @@ fn run(module: &Bound<'_, PyModule>) -> PyResult<()> { let builtins = PyModule::import(py, "builtins")?; let locals = builtins.getattr("locals")?.call0()?; - let setup_fn = locals.get_item("setup")?; - let draw_fn = locals.get_item("draw")?; + let mut setup_fn = locals.get_item("setup")?; + let mut draw_fn = locals.get_item("draw")?; // call setup setup_fn.call0()?; @@ -91,6 +97,30 @@ fn run(module: &Bound<'_, PyModule>) -> PyResult<()> { loop { { let mut graphics = get_graphics_mut(module)?; + + // TODO: this shouldn't be on the graphics object + let sketch = graphics.poll_for_sketch_update()?; + if !sketch.source.is_empty() { + let locals = PyDict::new(py); + + let ok = CString::new(sketch.source.as_str()).unwrap(); + let cstr: &CStr = ok.as_c_str(); + + match py.run(cstr, None, Some(&locals)) { + Ok(_) => { + dbg!("Success of any kind?"); + } + Err(e) => { + dbg!(e); + } + } + + setup_fn = locals.get_item("setup").unwrap().unwrap(); + draw_fn = locals.get_item("draw").unwrap().unwrap(); + + dbg!(locals); + } + if !graphics.surface.poll_events() { break; } diff --git a/crates/processing_render/src/config.rs b/crates/processing_render/src/config.rs index 75766a1..81ba5aa 100644 --- a/crates/processing_render/src/config.rs +++ b/crates/processing_render/src/config.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; #[derive(Clone, Hash, Eq, PartialEq)] pub enum ConfigKey { AssetRootPath, + SketchRootPath, } // TODO: Consider Box instead of String diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 6f60575..5006890 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -4,6 +4,7 @@ pub mod geometry; mod graphics; pub mod image; pub mod render; +pub mod sketch; mod surface; use std::{cell::RefCell, num::NonZero, path::PathBuf, sync::OnceLock}; @@ -212,6 +213,21 @@ fn create_app(config: Config) -> App { app.insert_resource(config.clone()); + if let Some(asset_path) = config.get(ConfigKey::AssetRootPath) { + app.register_asset_source( + "assets_directory", + AssetSourceBuilder::platform_default(asset_path, None), + ); + } + + if let Some(sketch_path) = config.get(ConfigKey::SketchRootPath) { + println!("DEBUG SKETCH PATH = {sketch_path}"); + app.register_asset_source( + "sketch_directory", + AssetSourceBuilder::platform_default(sketch_path, None), + ); + } + #[cfg(not(target_arch = "wasm32"))] let plugins = DefaultPlugins .build() @@ -234,14 +250,13 @@ fn create_app(config: Config) -> App { ..default() }); - if let Some(asset_path) = config.get(ConfigKey::AssetRootPath) { - app.register_asset_source( - "assets_directory", - AssetSourceBuilder::platform_default(asset_path, None), - ); + app.add_plugins(plugins); + + if let Some(_) = config.get(ConfigKey::SketchRootPath) { + info!("Adding plugin"); + app.add_plugins(sketch::LivecodePlugin); } - app.add_plugins(plugins); app.add_plugins(( ImagePlugin, GraphicsPlugin, @@ -1071,3 +1086,12 @@ pub fn geometry_box(width: f32, height: f32, depth: f32) -> error::Result error::Result> { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached(sketch::sketch_update_handler) + .unwrap()) + }) +} diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs new file mode 100644 index 0000000..79e35de --- /dev/null +++ b/crates/processing_render/src/sketch.rs @@ -0,0 +1,121 @@ +//! A Sketch asset represents a source file containing user code for a Processing sketch. + +use bevy::{ + asset::{ + AssetLoader, AssetPath, LoadContext, + io::{AssetSourceId, Reader}, + }, + prelude::*, +}; +use std::path::Path; + +#[derive(Resource)] +struct SketchNeedsReload(bool); + +/// Plugin that registers the Sketch asset type and its loader. +pub struct LivecodePlugin; + +impl Plugin for LivecodePlugin { + fn build(&self, app: &mut App) { + app.init_asset::() + .init_asset_loader::() + // TODO: this could be switched to Message + .insert_resource(SketchNeedsReload(false)) + .add_systems(PreStartup, load_current_sketch); + // .add_systems(Update, sketch_update_handler); + } +} + +// TODO: A better name is possible +pub fn sketch_update_handler( + mut events: MessageReader>, + sketches: Res>, +) -> Option { + for event in events.read() { + match event { + AssetEvent::Added { id } => { + info!("Added: {id}") + } + AssetEvent::Modified { id } => { + info!("Modified: {id}"); + // we want to emit some event to bevy?? + // needs_reload.0 = true; + if let Some(sketch) = sketches.get(*id) { + let sketch = sketch.clone(); + return Some(sketch); + } + } + AssetEvent::Removed { id } => { + info!("Removed: {id}") + } + AssetEvent::Unused { id } => { + info!("Unused: {id}") + } + AssetEvent::LoadedWithDependencies { id } => { + info!("LoadedWithDependencies: {id}") + } + } + } + + None +} + +fn load_current_sketch(mut commands: Commands, asset_server: Res) { + info!("DEBUG: calling load_current_sketch"); + let path = Path::new("rectangle.py"); + let source = AssetSourceId::from("sketch_directory"); + let asset_path = AssetPath::from_path(path).with_source(source); + let sketch_handle: Handle = asset_server.load(asset_path); + commands.spawn(SketchRoot(sketch_handle)); +} + +/// `SketchRoot` is what will be spawned and will contain a `Handle` to the `Sketch` +#[derive(Component)] +pub struct SketchRoot(pub Handle); + +/// A sketch source file loaded as a Bevy asset. +/// +/// The `Sketch` asset contains the raw source code as a string. It does not interpret +/// or execute the code — that responsibility belongs to language-specific crates. +#[derive(Asset, Clone, TypePath, Debug)] +pub struct Sketch { + // TODO: should this be &str ? + pub source: String, +} + +/// Loads sketch files from disk. +/// +/// Currently supports `.py` files, but the loader is designed to be extended +/// for other languages in the future. +#[derive(Default)] +pub struct SketchLoader; + +impl AssetLoader for SketchLoader { + type Asset = Sketch; + type Settings = (); + type Error = std::io::Error; + + async fn load( + &self, + reader: &mut dyn Reader, + _settings: &Self::Settings, + _load_context: &mut LoadContext<'_>, + ) -> Result { + let mut source = String::new(); + + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + + if let Ok(utf8) = str::from_utf8(&bytes) { + source = utf8.to_string(); + } + + info!(source); + + Ok(Sketch { source }) + } + + fn extensions(&self) -> &[&str] { + &["py"] + } +}