From b85878855039c521a8969d29ff066adc02f7f4fe Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Mon, 16 Dec 2024 08:35:17 +0100 Subject: [PATCH] Add pyodide_patch_runners, part 2 (remove js side) --- bindings/pyodide_web_demo/js/main.js | 1 - .../py_imgui_render/pyodide_imgui_render.py | 157 ------------------ 2 files changed, 158 deletions(-) delete mode 100644 bindings/pyodide_web_demo/py_imgui_render/pyodide_imgui_render.py diff --git a/bindings/pyodide_web_demo/js/main.js b/bindings/pyodide_web_demo/js/main.js index 927beb74..d2254e59 100644 --- a/bindings/pyodide_web_demo/js/main.js +++ b/bindings/pyodide_web_demo/js/main.js @@ -67,7 +67,6 @@ runButton.addEventListener('click', runEditorPythonCode); // ===================================== async function initialize() { await loadPyodideAndPackages(); - await load_pyodide_imgui_render(); await passCanvasToPyodide(); } diff --git a/bindings/pyodide_web_demo/py_imgui_render/pyodide_imgui_render.py b/bindings/pyodide_web_demo/py_imgui_render/pyodide_imgui_render.py deleted file mode 100644 index e1274936..00000000 --- a/bindings/pyodide_web_demo/py_imgui_render/pyodide_imgui_render.py +++ /dev/null @@ -1,157 +0,0 @@ -"""This module provides a way to render hello imgui and immapp applications in the browser using pyodide. -It works by monkey patching the `hello_imgui.run` and `immapp.run` functions to use a custom implementation -that integrates with the browser's requestAnimationFrame API. -""" -from dataclasses import dataclass -from typing import Callable -from imgui_bundle import hello_imgui, immapp -from enum import Enum -from pyodide.ffi import create_proxy # type: ignore -import js # type: ignore -import gc -import logging - -logging.basicConfig(level=logging.DEBUG) -logger = logging.getLogger("pyodide_imgui_render") -logger.setLevel(logging.INFO) - -def _log(msg: str): - # logger.info(msg) - # js.console.log("pyodide_imgui_render: " + msg) - pass - - -class _JsAnimationRenderer: - """Make it possible to call a python function to do rendering at each javascript frame.""" - render_fn: Callable[[], None] # A python function that performs rendering - stop_requested: bool # A flag to request the animation loop to stop - main_loop_proxy: js.Proxy # A javascript proxy to the main_loop method that is called at each frame - - def __init__(self, render_fn: Callable[[], None]): - self.render_fn = render_fn - self.stop_requested = False - self.main_loop_proxy = create_proxy(self.main_loop) - - def main_loop(self, _timestamp): - if self.stop_requested: - if self.main_loop_proxy: - _log("_JsAnimationRenderer: received stop_requested => destroying main_loop_proxy") - self.main_loop_proxy.destroy() - self.main_loop_proxy = None - return - try: - self.render_fn() - except Exception as e: - self.request_stop() - raise e - - # Schedule the next frame - js.requestAnimationFrame(self.main_loop_proxy) - - def start(self): - _log("_JsAnimationRenderer.start()") - self.stop_requested = False - js.requestAnimationFrame(self.main_loop_proxy) - - def request_stop(self): - _log("_JsAnimationRenderer:stop() => set stop_requested=True") - self.stop_requested = True - - -@dataclass -class _RenderLifeCycleFunctions: - setup: Callable[[], None] = None - render: Callable[[], None] = None - tear_down: Callable[[], None] = None - - -class _HelloImGuiOrImmApp(Enum): - HELLO_IMGUI = 1 - IMMAPP = 2 - - -def _arg_to_render_lifecycle_functions(himgui_or_immapp: _HelloImGuiOrImmApp, *args, **kwargs) -> _RenderLifeCycleFunctions: - """Converts the arguments to the correct render lifecycle functions, - depending on the type of arguments passed and whether it is a hello_imgui or immapp application.""" - functions = _RenderLifeCycleFunctions() - - if himgui_or_immapp == _HelloImGuiOrImmApp.HELLO_IMGUI: - render_module = hello_imgui.manual_render - elif himgui_or_immapp == _HelloImGuiOrImmApp.IMMAPP: - render_module = immapp.manual_render - else: - raise ValueError("Invalid value for himgui_or_immapp") - - _log(f"{len(args)=} args: {args} kwargs: {kwargs}") - use_runner_params = (len(args) >= 1 and isinstance(args[0], hello_imgui.RunnerParams)) or "runner_params" in kwargs - use_simple_params = (len(args) >= 1 and isinstance(args[0], hello_imgui.SimpleRunnerParams)) or "simple_params" in kwargs - use_gui_function = (len(args) >= 1 and callable(args[0])) or "gui_function" in kwargs - - if use_runner_params: - _log("overload with RunnerParams") - functions.setup = lambda: render_module.setup_from_runner_params(*args, **kwargs) - elif use_simple_params: - _log("overload with SimpleRunnerParams") - functions.setup = lambda: render_module.setup_from_simple_runner_params(*args, **kwargs) - elif use_gui_function: - _log("overload with callable") - functions.setup = lambda:render_module.setup_from_gui_function(*args, **kwargs) - else: - raise ValueError("Invalid arguments") - - functions.render = render_module.render - - functions.tear_down = render_module.tear_down - - return functions - - -class _ManualRenderJs: - """Manages the ManualRender lifecycle (from HelloImGui or ImmApp) and integrates with _JsAnimationRenderer.""" - js_animation_renderer: _JsAnimationRenderer | None = None - is_running: bool = False - render_lifecycle_functions: _RenderLifeCycleFunctions | None = None - - def _stop(self): - """Stops the current rendering loop and tears down the renderer.""" - if not self.is_running: - return - if self.js_animation_renderer is not None: - _log("_ManualRenderJs: Stopping js_animation_renderer") - self.js_animation_renderer.request_stop() - self.js_animation_renderer = None - - try: - self.render_lifecycle_functions.tear_down() - self.render_lifecycle_functions = None - _log("_ManualRenderJs: HelloImGuiRunnerJs: Renderer torn down successfully.") - except Exception as e: - import traceback - js.console.error(f"_ManualRenderJs: Error during Renderer teardown: {e}\n{traceback.format_exc()}") - finally: - # Force garbage collection to free resources - gc.collect() - - def _run(self, himgui_or_immapp: _HelloImGuiOrImmApp, *args, **kwargs): - if self.is_running: - self._stop() - self.is_running = True - - self.render_lifecycle_functions = _arg_to_render_lifecycle_functions(himgui_or_immapp, *args, **kwargs) - self.render_lifecycle_functions.setup() - self.js_animation_renderer = _JsAnimationRenderer(self.render_lifecycle_functions.render) - self.js_animation_renderer.start() - - def run_immapp(self, *args, **kwargs): - self._run(_HelloImGuiOrImmApp.IMMAPP, *args, **kwargs) - - def run_hello_imgui(self, *args, **kwargs): - self._run(_HelloImGuiOrImmApp.HELLO_IMGUI, *args, **kwargs) - - -# Instantiate global runners -_log("_MANUAL_RENDER_JS: Version 1") -_MANUAL_RENDER_JS = _ManualRenderJs() -# Monkey patch the hello_imgui.run and immapp.run function to use the js version -hello_imgui.run = _MANUAL_RENDER_JS.run_hello_imgui -immapp.run = _MANUAL_RENDER_JS.run_immapp