From 3e9cdaf53be528004df305b6032c5378a20a9b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Fri, 16 Jan 2026 21:52:07 -0500 Subject: [PATCH 01/11] Turn on file_watcher feature for Bevy --- Cargo.lock | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- 2 files changed, 170 insertions(+), 2 deletions(-) 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" } From 22a449e54e75da213f74f5e381591c166123f1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Sat, 17 Jan 2026 16:51:56 -0500 Subject: [PATCH 02/11] Add Sketch asset, and a LivecodePlugin --- crates/processing_render/src/sketch.rs | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 crates/processing_render/src/sketch.rs diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs new file mode 100644 index 0000000..0bc59c7 --- /dev/null +++ b/crates/processing_render/src/sketch.rs @@ -0,0 +1,76 @@ +//! A Sketch asset represents a source file containing user code for a Processing sketch. +//! +//! Sketches are loaded through Bevy's asset system, which provides automatic file watching +//! and change detection. This enables hot-reloading workflows where artists can edit their +//! sketch code and see changes reflected immediately without restarting. +//! +//! This module is intentionally language-agnostic — it only handles loading source text from +//! disk. Language-specific crates (like `processing_pyo3`) are responsible for executing the +//! source and binding it to the Processing API. + +use bevy::{ + asset::{AssetLoader, LoadContext, io::Reader}, + prelude::*, +}; +use std::path::PathBuf; + +/// 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::(); + } +} + +/// 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, TypePath, Debug)] +pub struct Sketch { + /// The source code contents of the sketch file. + pub source: String, + + /// The original file path. + pub path: PathBuf, +} + +/// 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(); + } + + let asset_path = load_context.path(); + let path: PathBuf = asset_path.path().to_path_buf(); + + Ok(Sketch { source, path }) + } + + fn extensions(&self) -> &[&str] { + &["py"] + } +} From a4ab04e4e578d3d161f45814a22a45bec7348470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Tue, 20 Jan 2026 08:53:19 -0500 Subject: [PATCH 03/11] Add SketchRootPath ConfigKey --- crates/processing_pyo3/src/graphics.rs | 5 +++++ crates/processing_render/src/config.rs | 1 + crates/processing_render/src/lib.rs | 10 +++++++++ crates/processing_render/src/sketch.rs | 28 ++++++++++++++++---------- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 95bef31..2b50d4d 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -121,6 +121,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 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 3b37229..0ba88e0 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; pub mod transform; @@ -243,6 +244,15 @@ fn create_app(config: Config) -> App { } app.add_plugins(plugins); + 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), + ) + .add_plugins(sketch::LivecodePlugin); + } + app.add_plugins(( ImagePlugin, GraphicsPlugin, diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 0bc59c7..4da828f 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -1,18 +1,13 @@ //! A Sketch asset represents a source file containing user code for a Processing sketch. -//! -//! Sketches are loaded through Bevy's asset system, which provides automatic file watching -//! and change detection. This enables hot-reloading workflows where artists can edit their -//! sketch code and see changes reflected immediately without restarting. -//! -//! This module is intentionally language-agnostic — it only handles loading source text from -//! disk. Language-specific crates (like `processing_pyo3`) are responsible for executing the -//! source and binding it to the Processing API. use bevy::{ - asset::{AssetLoader, LoadContext, io::Reader}, + asset::{ + AssetLoader, AssetPath, LoadContext, + io::{AssetSourceId, Reader}, + }, prelude::*, }; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; /// Plugin that registers the Sketch asset type and its loader. pub struct LivecodePlugin; @@ -20,10 +15,21 @@ pub struct LivecodePlugin; impl Plugin for LivecodePlugin { fn build(&self, app: &mut App) { app.init_asset::() - .init_asset_loader::(); + .init_asset_loader::() + .add_systems(Startup, load_current_sketch); } } +fn load_current_sketch(asset_server: Res) { + let path = Path::new("rectangle.py"); + let source = AssetSourceId::from("sketch_directory"); + let _asset_path = AssetPath::from_path(path).with_source(source); + + dbg!("OKOKOKOK {:?}", _asset_path); + + let _h: Handle = asset_server.load(path); +} + /// A sketch source file loaded as a Bevy asset. /// /// The `Sketch` asset contains the raw source code as a string. It does not interpret From 3c3d131b439b4f974e9a289deb9d01b9a1abad8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Thu, 22 Jan 2026 18:40:43 -0500 Subject: [PATCH 04/11] register_asset_source before plugin configuration --- crates/processing_render/src/lib.rs | 33 +++++++++++++++----------- crates/processing_render/src/sketch.rs | 16 +++++++++---- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 0ba88e0..5966a69 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -214,6 +214,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() @@ -236,21 +251,11 @@ 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(sketch_path) = config.get(ConfigKey::SketchRootPath) { - println!("DEBUG SKETCH PATH = {sketch_path}"); - app.register_asset_source( - "sketch_directory", - AssetSourceBuilder::platform_default(sketch_path, None), - ) - .add_plugins(sketch::LivecodePlugin); + + if let Some(_) = config.get(ConfigKey::SketchRootPath) { + info!("Adding plugin"); + app.add_plugins(sketch::LivecodePlugin); } app.add_plugins(( diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 4da828f..6cb0a8f 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -17,17 +17,23 @@ impl Plugin for LivecodePlugin { app.init_asset::() .init_asset_loader::() .add_systems(Startup, load_current_sketch); + // .add_systems(Update, test_system); + let render_app = app.sub_app_mut(bevy::render::RenderApp); + render_app.add_systems(ExtractSchedule, test_system); } } +fn test_system() { + info!("DEBUG: calling test_system"); + assert!(false); +} + fn load_current_sketch(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); - - dbg!("OKOKOKOK {:?}", _asset_path); - - let _h: Handle = asset_server.load(path); + let asset_path = AssetPath::from_path(path).with_source(source); + let _h: Handle = asset_server.load(asset_path); } /// A sketch source file loaded as a Bevy asset. From f293be2bb930cd417ffc21943451bd1b793f24e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Fri, 23 Jan 2026 18:27:23 -0500 Subject: [PATCH 05/11] Created SketchRoot to hold onto Handle fix AssetEvent handler for Sketch entities --- crates/processing_render/src/sketch.rs | 57 +++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 6cb0a8f..3f8337d 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -7,7 +7,7 @@ use bevy::{ }, prelude::*, }; -use std::path::{Path, PathBuf}; +use std::path::Path; /// Plugin that registers the Sketch asset type and its loader. pub struct LivecodePlugin; @@ -15,38 +15,56 @@ pub struct LivecodePlugin; impl Plugin for LivecodePlugin { fn build(&self, app: &mut App) { app.init_asset::() - .init_asset_loader::() - .add_systems(Startup, load_current_sketch); - // .add_systems(Update, test_system); - let render_app = app.sub_app_mut(bevy::render::RenderApp); - render_app.add_systems(ExtractSchedule, test_system); + .init_asset_loader::(); + + app.add_systems(PreStartup, load_current_sketch) + .add_systems(Update, sketch_update_handler); } } -fn test_system() { - info!("DEBUG: calling test_system"); - assert!(false); +// TODO: A better name is possible +fn sketch_update_handler(mut events: MessageReader>) { + for event in events.read() { + match event { + AssetEvent::Added { id } => { + info!("Added: {id}") + } + AssetEvent::Modified { id } => { + info!("Modified: {id}") + } + AssetEvent::Removed { id } => { + info!("Removed: {id}") + } + AssetEvent::Unused { id } => { + info!("Unused: {id}") + } + AssetEvent::LoadedWithDependencies { id } => { + info!("LoadedWithDependencies: {id}") + } + } + } } -fn load_current_sketch(asset_server: Res) { +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 _h: Handle = asset_server.load(asset_path); + 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, TypePath, Debug)] pub struct Sketch { - /// The source code contents of the sketch file. - pub source: String, - - /// The original file path. - pub path: PathBuf, + source: String, } /// Loads sketch files from disk. @@ -65,7 +83,7 @@ impl AssetLoader for SketchLoader { &self, reader: &mut dyn Reader, _settings: &Self::Settings, - load_context: &mut LoadContext<'_>, + _load_context: &mut LoadContext<'_>, ) -> Result { let mut source = String::new(); @@ -76,10 +94,9 @@ impl AssetLoader for SketchLoader { source = utf8.to_string(); } - let asset_path = load_context.path(); - let path: PathBuf = asset_path.path().to_path_buf(); + info!(source); - Ok(Sketch { source, path }) + Ok(Sketch { source }) } fn extensions(&self) -> &[&str] { From 6de9c3a8c424de19ae7e3dcde51fcb1b1f5119c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Sat, 24 Jan 2026 20:21:03 -0500 Subject: [PATCH 06/11] Set a needs update flag on sketch updates --- crates/processing_render/src/sketch.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 3f8337d..cf6637e 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -9,28 +9,36 @@ use bevy::{ }; 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::(); - - app.add_systems(PreStartup, load_current_sketch) + .init_asset_loader::() + .insert_resource(SketchNeedsReload(false)) + .add_systems(PreStartup, load_current_sketch) .add_systems(Update, sketch_update_handler); } } // TODO: A better name is possible -fn sketch_update_handler(mut events: MessageReader>) { +fn sketch_update_handler( + mut events: MessageReader>, + mut needs_reload: ResMut, +) { for event in events.read() { match event { AssetEvent::Added { id } => { info!("Added: {id}") } AssetEvent::Modified { id } => { - info!("Modified: {id}") + info!("Modified: {id}"); + // we want to emit some event to bevy?? + needs_reload.0 = true; } AssetEvent::Removed { id } => { info!("Removed: {id}") From 36a0df26fc876f66110c888c37c88f863773bb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Mon, 26 Jan 2026 08:14:08 -0500 Subject: [PATCH 07/11] Sketch reloading when running python --- crates/processing_pyo3/src/graphics.rs | 20 ++++++++++++++ crates/processing_pyo3/src/lib.rs | 36 +++++++++++++++++++++++--- crates/processing_render/src/lib.rs | 9 +++++++ crates/processing_render/src/sketch.rs | 24 +++++++++++------ 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 2b50d4d..3d7f89c 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] @@ -146,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/lib.rs b/crates/processing_render/src/lib.rs index 5966a69..14d37b6 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -1158,3 +1158,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 index cf6637e..79e35de 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -19,17 +19,18 @@ 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); + .add_systems(PreStartup, load_current_sketch); + // .add_systems(Update, sketch_update_handler); } } // TODO: A better name is possible -fn sketch_update_handler( +pub fn sketch_update_handler( mut events: MessageReader>, - mut needs_reload: ResMut, -) { + sketches: Res>, +) -> Option { for event in events.read() { match event { AssetEvent::Added { id } => { @@ -38,7 +39,11 @@ fn sketch_update_handler( AssetEvent::Modified { id } => { info!("Modified: {id}"); // we want to emit some event to bevy?? - needs_reload.0 = true; + // needs_reload.0 = true; + if let Some(sketch) = sketches.get(*id) { + let sketch = sketch.clone(); + return Some(sketch); + } } AssetEvent::Removed { id } => { info!("Removed: {id}") @@ -51,6 +56,8 @@ fn sketch_update_handler( } } } + + None } fn load_current_sketch(mut commands: Commands, asset_server: Res) { @@ -70,9 +77,10 @@ pub struct SketchRoot(pub Handle); /// /// 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, TypePath, Debug)] +#[derive(Asset, Clone, TypePath, Debug)] pub struct Sketch { - source: String, + // TODO: should this be &str ? + pub source: String, } /// Loads sketch files from disk. From 724386c5f9f84bdb8e8a65a740f5fd84a7f28fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Tue, 10 Feb 2026 19:55:41 -0500 Subject: [PATCH 08/11] Pass sketch file based on python file being executed Clean up debug statements Remove unused code --- crates/processing_pyo3/examples/rectangle.py | 4 ++-- crates/processing_pyo3/src/graphics.rs | 17 +++++++-------- crates/processing_pyo3/src/lib.rs | 21 ++++++++++++++++++- crates/processing_render/src/lib.rs | 1 - crates/processing_render/src/sketch.rs | 22 +------------------- 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/crates/processing_pyo3/examples/rectangle.py b/crates/processing_pyo3/examples/rectangle.py index ac03477..fe362d1 100644 --- a/crates/processing_pyo3/examples/rectangle.py +++ b/crates/processing_pyo3/examples/rectangle.py @@ -7,8 +7,8 @@ def draw(): background(220) fill(255, 0, 100) - stroke(0) - stroke_weight(2) + stroke(1) + stroke_weight(25) rect(100, 100, 200, 150) # TODO: this should happen implicitly on module load somehow diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 3d7f89c..a977ba5 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -120,17 +120,18 @@ impl Drop for Graphics { #[pymethods] impl Graphics { #[new] - pub fn new(width: u32, height: u32, asset_path: &str) -> PyResult { + pub fn new( + width: u32, + height: u32, + asset_path: &str, + sketch_root_path: &str, + ) -> PyResult { let glfw_ctx = GlfwContext::new(width, height).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; 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(), - ); + config.set(ConfigKey::SketchRootPath, sketch_root_path.to_string()); init(config).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let surface = glfw_ctx @@ -160,10 +161,6 @@ impl Graphics { source: "".to_string(), }), } - - // Ok(Sketch { - // source: sketch.unwrap().source, - // }) } pub fn background(&self, args: Vec) -> PyResult<()> { diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index fb088fd..9f4e9bc 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -71,11 +71,30 @@ fn get_asset_root() -> PyResult { }) } +fn get_sketch_root() -> PyResult { + Python::attach(|py| { + let sys = PyModule::import(py, "sys")?; + let argv: Vec = sys.getattr("argv")?.extract()?; + let filename: &str = argv[0].as_str(); + let os = PyModule::import(py, "os")?; + let path = os.getattr("path")?; + let dirname = path.getattr("dirname")?.call1((filename,))?; + let abspath = path.getattr("abspath")?.call1((dirname,))?; + Ok(abspath.to_string()) + }) +} + #[pyfunction] #[pyo3(pass_module)] fn size(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult<()> { let asset_path: String = get_asset_root()?; - let graphics = Graphics::new(width, height, asset_path.as_str())?; + let sketch_root_path: String = get_sketch_root()?; + let graphics = Graphics::new( + width, + height, + asset_path.as_str(), + sketch_root_path.as_str(), + )?; module.setattr("_graphics", graphics)?; Ok(()) } diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 14d37b6..e31dc3b 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -222,7 +222,6 @@ fn create_app(config: Config) -> App { } 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), diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 79e35de..fb04613 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -9,9 +9,6 @@ use bevy::{ }; use std::path::Path; -#[derive(Resource)] -struct SketchNeedsReload(bool); - /// Plugin that registers the Sketch asset type and its loader. pub struct LivecodePlugin; @@ -19,10 +16,7 @@ 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); } } @@ -33,27 +27,14 @@ pub fn sketch_update_handler( ) -> 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}") - } + _ => (), } } @@ -61,7 +42,6 @@ pub fn sketch_update_handler( } 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); From 30a855f432db50535afd0a890d35d59bb167981b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Tue, 10 Feb 2026 20:32:51 -0500 Subject: [PATCH 09/11] Pass sketch filename to `Sketch` --- crates/processing_pyo3/src/graphics.rs | 2 ++ crates/processing_pyo3/src/lib.rs | 15 ++++++++++++++- crates/processing_render/src/config.rs | 1 + crates/processing_render/src/sketch.rs | 13 +++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index a977ba5..0dab690 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -125,6 +125,7 @@ impl Graphics { height: u32, asset_path: &str, sketch_root_path: &str, + sketch_file_name: &str, ) -> PyResult { let glfw_ctx = GlfwContext::new(width, height).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; @@ -132,6 +133,7 @@ impl Graphics { let mut config = Config::new(); config.set(ConfigKey::AssetRootPath, asset_path.to_string()); config.set(ConfigKey::SketchRootPath, sketch_root_path.to_string()); + config.set(ConfigKey::SketchFileName, sketch_file_name.to_string()); init(config).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; let surface = glfw_ctx diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 9f4e9bc..32e0c93 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -14,7 +14,6 @@ mod graphics; use graphics::{Geometry, Graphics, Image, Topology, get_graphics, get_graphics_mut}; use pyo3::{ exceptions::PyRuntimeError, - ffi::c_str, prelude::*, types::{PyDict, PyTuple}, }; @@ -84,16 +83,30 @@ fn get_sketch_root() -> PyResult { }) } +fn get_sketch_filename() -> PyResult { + Python::attach(|py| { + let sys = PyModule::import(py, "sys")?; + let argv: Vec = sys.getattr("argv")?.extract()?; + let filename: &str = argv[0].as_str(); + let os = PyModule::import(py, "os")?; + let path = os.getattr("path")?; + let basename = path.getattr("basename")?.call1((filename,))?; + Ok(basename.to_string()) + }) +} + #[pyfunction] #[pyo3(pass_module)] fn size(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult<()> { let asset_path: String = get_asset_root()?; let sketch_root_path: String = get_sketch_root()?; + let sketch_filename: String = get_sketch_filename()?; let graphics = Graphics::new( width, height, asset_path.as_str(), sketch_root_path.as_str(), + sketch_filename.as_str(), )?; module.setattr("_graphics", graphics)?; Ok(()) diff --git a/crates/processing_render/src/config.rs b/crates/processing_render/src/config.rs index 81ba5aa..f4a91ce 100644 --- a/crates/processing_render/src/config.rs +++ b/crates/processing_render/src/config.rs @@ -9,6 +9,7 @@ use std::collections::HashMap; pub enum ConfigKey { AssetRootPath, SketchRootPath, + SketchFileName, } // TODO: Consider Box instead of String diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index fb04613..a2e3612 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -9,6 +9,8 @@ use bevy::{ }; use std::path::Path; +use crate::config::{Config, ConfigKey}; + /// Plugin that registers the Sketch asset type and its loader. pub struct LivecodePlugin; @@ -41,8 +43,15 @@ pub fn sketch_update_handler( None } -fn load_current_sketch(mut commands: Commands, asset_server: Res) { - let path = Path::new("rectangle.py"); +fn load_current_sketch( + mut commands: Commands, + asset_server: Res, + config: Res, +) { + let filename = config + .get(ConfigKey::SketchFileName) + .expect("SketchFileName not set"); + let path = Path::new(filename); 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); From c93fd2053967add96a434fa4abd615dd5808b649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Tue, 10 Feb 2026 20:35:21 -0500 Subject: [PATCH 10/11] get_sketch_info function --- crates/processing_pyo3/examples/rectangle.py | 2 +- crates/processing_pyo3/src/lib.rs | 20 +++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/crates/processing_pyo3/examples/rectangle.py b/crates/processing_pyo3/examples/rectangle.py index fe362d1..e8b1cb1 100644 --- a/crates/processing_pyo3/examples/rectangle.py +++ b/crates/processing_pyo3/examples/rectangle.py @@ -8,7 +8,7 @@ def draw(): fill(255, 0, 100) stroke(1) - stroke_weight(25) + stroke_weight(2) rect(100, 100, 200, 150) # TODO: this should happen implicitly on module load somehow diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 32e0c93..18ceb5a 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -70,7 +70,7 @@ fn get_asset_root() -> PyResult { }) } -fn get_sketch_root() -> PyResult { +fn get_sketch_info() -> PyResult<(String, String)> { Python::attach(|py| { let sys = PyModule::import(py, "sys")?; let argv: Vec = sys.getattr("argv")?.extract()?; @@ -79,19 +79,8 @@ fn get_sketch_root() -> PyResult { let path = os.getattr("path")?; let dirname = path.getattr("dirname")?.call1((filename,))?; let abspath = path.getattr("abspath")?.call1((dirname,))?; - Ok(abspath.to_string()) - }) -} - -fn get_sketch_filename() -> PyResult { - Python::attach(|py| { - let sys = PyModule::import(py, "sys")?; - let argv: Vec = sys.getattr("argv")?.extract()?; - let filename: &str = argv[0].as_str(); - let os = PyModule::import(py, "os")?; - let path = os.getattr("path")?; let basename = path.getattr("basename")?.call1((filename,))?; - Ok(basename.to_string()) + Ok((abspath.to_string(), basename.to_string())) }) } @@ -99,8 +88,9 @@ fn get_sketch_filename() -> PyResult { #[pyo3(pass_module)] fn size(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult<()> { let asset_path: String = get_asset_root()?; - let sketch_root_path: String = get_sketch_root()?; - let sketch_filename: String = get_sketch_filename()?; + let (sketch_root_path, sketch_filename) = get_sketch_info()?; + // let sketch_root_path: String = get_sketch_root()?; + // let sketch_filename: String = get_sketch_filename()?; let graphics = Graphics::new( width, height, From ff5e44b0a055d78995679d7b9c094736511ac70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moon=20Dav=C3=A9?= Date: Tue, 10 Feb 2026 20:43:01 -0500 Subject: [PATCH 11/11] clippy --- crates/processing_render/src/lib.rs | 2 +- crates/processing_render/src/sketch.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index e31dc3b..910a834 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -252,7 +252,7 @@ fn create_app(config: Config) -> App { app.add_plugins(plugins); - if let Some(_) = config.get(ConfigKey::SketchRootPath) { + if config.get(ConfigKey::SketchRootPath).is_some() { info!("Adding plugin"); app.add_plugins(sketch::LivecodePlugin); } diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index a2e3612..e353c30 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -28,15 +28,12 @@ pub fn sketch_update_handler( sketches: Res>, ) -> Option { for event in events.read() { - match event { - AssetEvent::Modified { id } => { - info!("Modified: {id}"); - if let Some(sketch) = sketches.get(*id) { - let sketch = sketch.clone(); - return Some(sketch); - } + if let AssetEvent::Modified { id } = event { + info!("Modified: {id}"); + if let Some(sketch) = sketches.get(*id) { + let sketch = sketch.clone(); + return Some(sketch); } - _ => (), } }