@@ -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
175261fn 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