diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index 3768e3f25..ff1f1071b 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -1211,7 +1211,7 @@ def show_viewport(*, minimized: bool ='', maximized: bool ='') -> None: """Shows the main viewport.""" ... -def split_frame(*, delay: int ='') -> None: +def split_frame() -> None: """Waits one frame.""" ... diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index 65f4f23e9..ae46b3390 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -8688,16 +8688,16 @@ def show_viewport(**kwargs): return internal_dpg.show_viewport(**kwargs) -def split_frame(**kwargs): +def split_frame(): """ Waits one frame. Args: - delay (int, optional): Minimal delay in in milliseconds + delay (int, optional): (deprecated)Do not use it anymore, it has no effect. Returns: None """ - return internal_dpg.split_frame(**kwargs) + return internal_dpg.split_frame() def stop_dearpygui(): """ Stops Dear PyGui diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index 6c69a5027..ed2e451ab 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -9661,16 +9661,22 @@ def show_viewport(*, minimized: bool =False, maximized: bool =False, **kwargs) - return internal_dpg.show_viewport(minimized=minimized, maximized=maximized, **kwargs) -def split_frame(*, delay: int =32, **kwargs) -> None: +def split_frame(**kwargs) -> None: """ Waits one frame. Args: - delay (int, optional): Minimal delay in in milliseconds + delay (int, optional): (deprecated) Do not use it anymore, it has no effect. Returns: None """ - return internal_dpg.split_frame(delay=delay, **kwargs) + if 'delay' in kwargs.keys(): + + warnings.warn('delay keyword removed', DeprecationWarning, 2) + + kwargs.pop('delay', None) + + return internal_dpg.split_frame(**kwargs) def stop_dearpygui(**kwargs) -> None: """ Stops Dear PyGui diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index cc81f4bf7..86bb62e94 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -2271,19 +2271,26 @@ save_init_file(PyObject* self, PyObject* args, PyObject* kwargs) static PyObject* split_frame(PyObject* self, PyObject* args, PyObject* kwargs) { - i32 delay = 32; - - if (!Parse((GetParsers())["split_frame"], args, kwargs, __FUNCTION__, - &delay)) + if (!Parse((GetParsers())["split_frame"], args, kwargs, __FUNCTION__)) return GetPyNone(); - // std::lock_guard lk(GContext->mutex); + if (GContext->running) + { + Py_BEGIN_ALLOW_THREADS; + std::unique_lock lk(GContext->frameEndedMutex); + GContext->frameEnded = false; + GContext->frameEndedEvent.wait(lk, []{return GContext->frameEnded;}); + lk.unlock(); - Py_BEGIN_ALLOW_THREADS; - GContext->waitOneFrame = true; - while (GContext->waitOneFrame) - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); - Py_END_ALLOW_THREADS; + Py_END_ALLOW_THREADS; + } + + // Now let's see if it was successful (there's a chance that DPG got stopped while we were waiting) + if (!GContext->running) + { + mvThrowPythonError(mvErrorCode::mvNone, "split_frame is exiting: there is no active rendering loop."); + return nullptr; + } return GetPyNone(); } @@ -2654,7 +2661,7 @@ destroy_context(PyObject* self, PyObject* args, PyObject* kwargs) else { // Make sure everyone knows we're shutting down, even if stop_dearpygui - // was not called. + // was not called. This also releases any waiting split_frame calls. StopRendering(); Py_BEGIN_ALLOW_THREADS; diff --git a/src/dearpygui_parsers.h b/src/dearpygui_parsers.h index ef229ae01..0d6d4d745 100644 --- a/src/dearpygui_parsers.h +++ b/src/dearpygui_parsers.h @@ -564,7 +564,7 @@ InsertParser_Block1(std::map& parsers) { std::vector args; - args.push_back({ mvPyDataType::Integer, "delay", mvArgType::KEYWORD_ARG, "32", "Minimal delay in in milliseconds" }); + args.push_back({ mvPyDataType::Integer, "delay", mvArgType::DEPRECATED_REMOVE_KEYWORD_ARG, "32", "Do not use it anymore, it has no effect." }); mvPythonParserSetup setup; setup.about = "Waits one frame."; diff --git a/src/mvContext.cpp b/src/mvContext.cpp index cad61d205..cc41d22bc 100644 --- a/src/mvContext.cpp +++ b/src/mvContext.cpp @@ -182,20 +182,22 @@ Render() mvToolManager::Draw(); + if (GContext->resetTheme) { - if (GContext->resetTheme) - { - SetDefaultTheme(); - GContext->resetTheme = false; - } - - mvRunTasks(); - RenderItemRegistry(*GContext->itemRegistry); - mvRunTasks(); + SetDefaultTheme(); + GContext->resetTheme = false; } - if (GContext->waitOneFrame == true) - GContext->waitOneFrame = false; + mvRunTasks(); + RenderItemRegistry(*GContext->itemRegistry); + mvRunTasks(); + + // release split_frame if it's waiting for the frame end + { + std::lock_guard lk(GContext->frameEndedMutex); + GContext->frameEnded = true; + } + GContext->frameEndedEvent.notify_all(); } std::map& @@ -206,7 +208,20 @@ GetParsers() void StopRendering() { + // While it may seem reasonable to set it to false with frameEndedMutex locked + // (to set it simultaneously with frameEnded), we don't want to spur another + // race condition between split_frame() and is_dearpygui_running() in the + // rendering loop. Let's trigger it as early as possible. GContext->running = false; + + // Unblock handlers (or other code) that might be waiting in `split_frame()` + { + std::lock_guard lk(GContext->frameEndedMutex); + // Simulate an end of frame even though there's no real frame this time. + // Otherwise split_frame will continue waiting. + GContext->frameEnded = true; + } + GContext->frameEndedEvent.notify_all(); } void diff --git a/src/mvContext.h b/src/mvContext.h index bed6fd2da..cb2bc864e 100644 --- a/src/mvContext.h +++ b/src/mvContext.h @@ -102,7 +102,9 @@ struct mvIO struct mvContext { - std::atomic_bool waitOneFrame = false; + std::mutex frameEndedMutex; + std::condition_variable frameEndedEvent; + bool frameEnded = false; // Indicates whether DPG has started at least once in this context, i.e. whether // associated Dear ImGui contexts exist and can be read from. std::atomic_bool started = false;