run_async_overloads.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle
  2. """Async support for ImGui Bundle - enables non-blocking GUI execution."""
  3. import asyncio
  4. from typing import Any, Optional, Callable, Tuple, overload
  5. from imgui_bundle.immapp import AddOnsParams
  6. from imgui_bundle.hello_imgui import RunnerParams, SimpleRunnerParams
  7. @overload
  8. async def run_async(
  9. runner_params: RunnerParams,
  10. addons_params: Optional[AddOnsParams] = None,
  11. ) -> None:
  12. """Run an application asynchronously using RunnerParams and AddOnsParams.
  13. This function will run until the application exits (user closes window or app_shall_exit is set).
  14. Use this when you need full control over the async lifecycle.
  15. Example:
  16. async def my_app():
  17. runner = hello_imgui.RunnerParams()
  18. runner.callbacks.show_gui = my_gui_function
  19. await immapp.run_async(runner)
  20. print("GUI closed")
  21. asyncio.run(my_app())
  22. """
  23. ...
  24. @overload
  25. async def run_async(
  26. simple_params: SimpleRunnerParams,
  27. addons_params: Optional[AddOnsParams] = None,
  28. ) -> None:
  29. """Run an application asynchronously using SimpleRunnerParams and AddOnsParams."""
  30. ...
  31. @overload
  32. async def run_async(
  33. gui_function: Callable[[], None],
  34. window_title: str = "",
  35. window_size_auto: bool = False,
  36. window_restore_previous_geometry: bool = False,
  37. window_size: Optional[Tuple[int, int]] = None,
  38. fps_idle: float = 10.0,
  39. top_most: bool = False,
  40. ini_disable: bool = False,
  41. with_implot: bool = False,
  42. with_implot3d: bool = False,
  43. with_markdown: bool = False,
  44. with_node_editor: bool = False,
  45. with_tex_inspect: bool = False,
  46. with_latex: bool = False,
  47. ) -> None:
  48. """Run an application asynchronously using a simple GUI function and parameters."""
  49. ...
  50. async def run_async(*args: Any, **kwargs: Any) -> None:
  51. """Run an ImGui application asynchronously in a non-blocking way.
  52. This function provides async/await support for ImGui Bundle applications.
  53. It will run until the application exits (user closes window or app_shall_exit is set).
  54. Three signatures are supported:
  55. 1. run_async(runner_params: RunnerParams, addons_params: Optional[AddOnsParams] = None)
  56. 2. run_async(simple_params: SimpleRunnerParams, addons_params: Optional[AddOnsParams] = None)
  57. 3. run_async(gui_function, window_title="", ..., with_implot=False, ...)
  58. For Jupyter notebook usage, see immapp.nb.start() for a more convenient API.
  59. """
  60. # Import here to avoid issues at module load time
  61. from imgui_bundle import hello_imgui
  62. from imgui_bundle.immapp import manual_render
  63. # Determine which setup method to use based on arguments
  64. if len(args) >= 1:
  65. first_arg = args[0]
  66. # Case 1: RunnerParams
  67. if isinstance(first_arg, RunnerParams):
  68. runner_params = first_arg
  69. addons_params = args[1] if len(args) > 1 else kwargs.get("addons_params", None)
  70. if addons_params is None:
  71. addons_params = AddOnsParams()
  72. manual_render.setup_from_runner_params(runner_params, addons_params)
  73. # Case 2: SimpleRunnerParams
  74. elif isinstance(first_arg, SimpleRunnerParams):
  75. simple_params = first_arg
  76. addons_params = args[1] if len(args) > 1 else kwargs.get("addons_params", None)
  77. if addons_params is None:
  78. addons_params = AddOnsParams()
  79. manual_render.setup_from_simple_runner_params(simple_params, addons_params)
  80. # Case 3: GUI function with keyword arguments
  81. elif callable(first_arg):
  82. gui_function = first_arg
  83. # Extract parameters, using defaults from immapp.run signature
  84. window_title = kwargs.get("window_title", "")
  85. window_size_auto = kwargs.get("window_size_auto", False)
  86. window_restore_previous_geometry = kwargs.get("window_restore_previous_geometry", False)
  87. window_size = kwargs.get("window_size", None)
  88. fps_idle = kwargs.get("fps_idle", 10.0)
  89. top_most = kwargs.get("top_most", False)
  90. ini_disable = kwargs.get("ini_disable", False)
  91. with_implot = kwargs.get("with_implot", False)
  92. with_implot3d = kwargs.get("with_implot3d", False)
  93. with_markdown = kwargs.get("with_markdown", False)
  94. with_node_editor = kwargs.get("with_node_editor", False)
  95. with_tex_inspect = kwargs.get("with_tex_inspect", False)
  96. with_latex = kwargs.get("with_latex", False)
  97. # with_latex implies with_markdown
  98. if with_latex:
  99. with_markdown = True
  100. manual_render.setup_from_gui_function(
  101. gui_function,
  102. window_title=window_title,
  103. window_size_auto=window_size_auto,
  104. window_restore_previous_geometry=window_restore_previous_geometry,
  105. window_size=window_size,
  106. fps_idle=fps_idle,
  107. top_most=top_most,
  108. ini_disable=ini_disable,
  109. with_implot=with_implot,
  110. with_implot3d=with_implot3d,
  111. with_markdown=with_markdown,
  112. with_node_editor=with_node_editor,
  113. with_tex_inspect=with_tex_inspect,
  114. with_latex=with_latex,
  115. )
  116. else:
  117. raise TypeError(f"First argument must be RunnerParams, SimpleRunnerParams, or a callable GUI function, got {type(first_arg)}")
  118. else:
  119. raise TypeError("run_async() requires at least one argument")
  120. # Configure FPS settings for optimal async performance
  121. # EarlyReturn mode ensures C++ code never blocks: it returns immediately
  122. # and tells Python how long to wait (see _priv_idle_frame_wait_duration_for_python_async_io)
  123. params = hello_imgui.get_runner_params()
  124. params.fps_idling.fps_idling_mode = hello_imgui.FpsIdlingMode.early_return
  125. params.fps_idling.vsync_to_monitor = False # vsync is implemented via sleep in the backend
  126. if params.fps_idling.fps_max <= 0.0:
  127. params.fps_idling.fps_max = 60.0 # default cap to avoid 1000+ FPS spins
  128. # Async render loop
  129. try:
  130. while not hello_imgui.get_runner_params().app_shall_exit:
  131. manual_render.render()
  132. wait_time = hello_imgui._priv_idle_frame_wait_duration_for_python_async_io() # type: ignore
  133. if wait_time > 0.0:
  134. await asyncio.sleep(wait_time)
  135. else:
  136. await asyncio.sleep(0)
  137. finally:
  138. # Ensure cleanup happens even if an exception occurs
  139. manual_render.tear_down()