Skip to content

Commit 6e8941c

Browse files
committed
Clearify setup/draw semantics (make both optional).
1 parent 370124f commit 6e8941c

1 file changed

Lines changed: 140 additions & 89 deletions

File tree

  • crates/processing_pyo3/src

crates/processing_pyo3/src/lib.rs

Lines changed: 140 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,100 @@ fn dispatch_event_callbacks(locals: &Bound<'_, PyAny>) -> PyResult<()> {
162162
Ok(())
163163
}
164164

165-
/// Get a shared ref to the Graphics context, or return Ok(()) if not yet initialized.
166-
macro_rules! graphics {
167-
($module:expr) => {
168-
match get_graphics($module)? {
169-
Some(g) => g,
170-
None => return Ok(()),
165+
fn create_graphics_context(
166+
module: &Bound<'_, PyModule>,
167+
width: u32,
168+
height: u32,
169+
) -> PyResult<()> {
170+
let py = module.py();
171+
let env = detect_environment(py)?;
172+
173+
let interactive = env != "script";
174+
let log_level = if interactive { Some("error") } else { None };
175+
176+
let has_existing = module
177+
.getattr("_graphics")
178+
.ok()
179+
.map(|a| !a.is_none())
180+
.unwrap_or(false);
181+
if has_existing {
182+
module.setattr("_graphics", py.None())?;
183+
}
184+
185+
match env.as_str() {
186+
"jupyter" => {
187+
let asset_path = get_asset_root()?;
188+
let graphics =
189+
Graphics::new_offscreen(width, height, asset_path.as_str(), log_level)?;
190+
module.setattr("_graphics", graphics)?;
191+
192+
if !has_existing {
193+
let code = CString::new(JUPYTER_POST_EXECUTE_CODE)?;
194+
py.run(code.as_c_str(), None, None).map_err(|e| {
195+
PyRuntimeError::new_err(format!("Failed to register Jupyter hooks: {e}"))
196+
})?;
197+
}
198+
}
199+
"ipython" => {
200+
let asset_path = get_asset_root()?;
201+
let (sketch_root, sketch_file) = get_sketch_info()?;
202+
let graphics = Graphics::new(
203+
width,
204+
height,
205+
asset_path.as_str(),
206+
sketch_root.as_str(),
207+
sketch_file.as_str(),
208+
log_level,
209+
)?;
210+
module.setattr("_graphics", graphics)?;
211+
212+
if !has_existing {
213+
let hook_code = CString::new(REGISTER_INPUTHOOK_CODE)?;
214+
py.run(hook_code.as_c_str(), None, None).map_err(|e| {
215+
PyRuntimeError::new_err(format!("Failed to register inputhook: {e}"))
216+
})?;
217+
218+
let post_code = CString::new(IPYTHON_POST_EXECUTE_CODE)?;
219+
py.run(post_code.as_c_str(), None, None).map_err(|e| {
220+
PyRuntimeError::new_err(format!(
221+
"Failed to register post-execute hook: {e}"
222+
))
223+
})?;
224+
}
225+
}
226+
_ => {
227+
let asset_path = get_asset_root()?;
228+
let (sketch_root, sketch_file) = get_sketch_info()?;
229+
let graphics = Graphics::new(
230+
width,
231+
height,
232+
asset_path.as_str(),
233+
sketch_root.as_str(),
234+
sketch_file.as_str(),
235+
log_level,
236+
)?;
237+
module.setattr("_graphics", graphics)?;
171238
}
172-
};
239+
}
240+
241+
Ok(())
242+
}
243+
244+
const DEFAULT_WIDTH: u32 = 100;
245+
const DEFAULT_HEIGHT: u32 = 100;
246+
247+
fn ensure_graphics(module: &Bound<'_, PyModule>) -> PyResult<()> {
248+
if get_graphics(module)?.is_some() {
249+
return Ok(());
250+
}
251+
create_graphics_context(module, DEFAULT_WIDTH, DEFAULT_HEIGHT)
252+
}
253+
254+
macro_rules! graphics {
255+
($module:expr) => {{
256+
ensure_graphics($module)?;
257+
get_graphics($module)?.expect("ensure_graphics guarantees Some")
258+
}};
173259
}
174260

175261
fn get_asset_root() -> PyResult<String> {
@@ -518,6 +604,10 @@ mod mewnala {
518604
#[pymodule_init]
519605
fn init(module: &Bound<'_, PyModule>) -> PyResult<()> {
520606
use processing::prelude::BlendMode;
607+
608+
module.add("width", super::DEFAULT_WIDTH)?;
609+
module.add("height", super::DEFAULT_HEIGHT)?;
610+
521611
module.add("BLEND", PyBlendMode::from_preset(BlendMode::Blend))?;
522612
module.add("ADD", PyBlendMode::from_preset(BlendMode::Add))?;
523613
module.add("SUBTRACT", PyBlendMode::from_preset(BlendMode::Subtract))?;
@@ -759,81 +849,9 @@ mod mewnala {
759849
#[pyfunction]
760850
#[pyo3(pass_module)]
761851
fn size(module: &Bound<'_, PyModule>, width: u32, height: u32) -> PyResult<()> {
762-
let py = module.py();
763-
let env = detect_environment(py)?;
764-
765-
let interactive = env != "script";
766-
let log_level = if interactive { Some("error") } else { None };
767-
768-
// Check if we already have a graphics context (i.e. size() was called before).
769-
// Drop the old one first so the window and GPU resources are released.
770-
let has_existing = module
771-
.getattr("_graphics")
772-
.ok()
773-
.map(|a| !a.is_none())
774-
.unwrap_or(false);
775-
if has_existing {
776-
module.setattr("_graphics", py.None())?;
777-
}
778-
779-
match env.as_str() {
780-
"jupyter" => {
781-
let asset_path = get_asset_root()?;
782-
let graphics =
783-
Graphics::new_offscreen(width, height, asset_path.as_str(), log_level)?;
784-
module.setattr("_graphics", graphics)?;
785-
786-
if !has_existing {
787-
let code = CString::new(JUPYTER_POST_EXECUTE_CODE)?;
788-
py.run(code.as_c_str(), None, None).map_err(|e| {
789-
PyRuntimeError::new_err(format!("Failed to register Jupyter hooks: {e}"))
790-
})?;
791-
}
792-
}
793-
"ipython" => {
794-
let asset_path = get_asset_root()?;
795-
let (sketch_root, sketch_file) = get_sketch_info()?;
796-
let graphics = Graphics::new(
797-
width,
798-
height,
799-
asset_path.as_str(),
800-
sketch_root.as_str(),
801-
sketch_file.as_str(),
802-
log_level,
803-
)?;
804-
module.setattr("_graphics", graphics)?;
805-
806-
if !has_existing {
807-
let hook_code = CString::new(REGISTER_INPUTHOOK_CODE)?;
808-
py.run(hook_code.as_c_str(), None, None).map_err(|e| {
809-
PyRuntimeError::new_err(format!("Failed to register inputhook: {e}"))
810-
})?;
811-
812-
let post_code = CString::new(IPYTHON_POST_EXECUTE_CODE)?;
813-
py.run(post_code.as_c_str(), None, None).map_err(|e| {
814-
PyRuntimeError::new_err(format!(
815-
"Failed to register post-execute hook: {e}"
816-
))
817-
})?;
818-
}
819-
}
820-
821-
// this is the default "script" mode where we assume the user will call run() to start the draw loop
822-
_ => {
823-
let asset_path = get_asset_root()?;
824-
let (sketch_root, sketch_file) = get_sketch_info()?;
825-
let graphics = Graphics::new(
826-
width,
827-
height,
828-
asset_path.as_str(),
829-
sketch_root.as_str(),
830-
sketch_file.as_str(),
831-
log_level,
832-
)?;
833-
module.setattr("_graphics", graphics)?;
834-
}
835-
}
852+
create_graphics_context(module, width, height)?;
836853

854+
let py = module.py();
837855
let sys = PyModule::import(py, "sys")?;
838856
let frame = sys.getattr("_getframe")?.call1((0,))?;
839857
let caller_globals = frame.getattr("f_globals")?;
@@ -857,15 +875,48 @@ mod mewnala {
857875
let builtins = PyModule::import(py, "builtins")?;
858876
let locals = builtins.getattr("locals")?.call0()?;
859877

860-
let setup_fn = locals.get_item("setup")?;
861-
let mut draw_fn = locals.get_item("draw")?;
878+
let setup_fn = locals.get_item("setup").ok();
879+
let mut draw_fn = locals.get_item("draw").ok();
862880

863-
// call setup
864-
setup_fn.call0()?;
881+
if let Some(ref setup) = setup_fn {
882+
setup.call0()?;
883+
}
865884

866-
let mut globals = draw_fn.getattr("__globals__")?;
885+
ensure_graphics(module)?;
886+
887+
let mut globals = if let Some(ref draw) = draw_fn {
888+
draw.getattr("__globals__")?
889+
} else if let Some(ref setup) = setup_fn {
890+
setup.getattr("__globals__")?
891+
} else {
892+
let sys = PyModule::import(py, "sys")?;
893+
let frame = sys.getattr("_getframe")?.call1((0,))?;
894+
frame.getattr("f_globals")?
895+
};
867896
sync_globals(module, &globals)?;
868897

898+
// if there's no draw fn, we enter an idle loop
899+
if draw_fn.is_none() {
900+
get_graphics(module)?
901+
.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?
902+
.end_draw()?;
903+
904+
loop {
905+
{
906+
let mut graphics = get_graphics_mut(module)?
907+
.ok_or_else(|| PyRuntimeError::new_err("call size() first"))?;
908+
if !graphics.surface.poll_events() {
909+
break;
910+
}
911+
}
912+
dispatch_event_callbacks(&locals)?;
913+
std::thread::sleep(std::time::Duration::from_millis(16));
914+
}
915+
916+
return Ok(());
917+
}
918+
let draw_fn_ref = draw_fn.as_mut().expect("checked above");
919+
869920
// start draw loop
870921
loop {
871922
{
@@ -887,8 +938,8 @@ mod mewnala {
887938
}
888939
}
889940

890-
draw_fn = locals.get_item("draw").unwrap().unwrap();
891-
globals = draw_fn.getattr("__globals__")?;
941+
*draw_fn_ref = locals.get_item("draw").unwrap().unwrap();
942+
globals = draw_fn_ref.getattr("__globals__")?;
892943
reset_tracked_globals();
893944

894945
dbg!(locals);
@@ -920,7 +971,7 @@ mod mewnala {
920971

921972
sync_globals(module, &globals)?;
922973

923-
draw_fn
974+
draw_fn_ref
924975
.call0()
925976
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
926977

0 commit comments

Comments
 (0)