@@ -2006,19 +2006,13 @@ set_frame_callback(PyObject* self, PyObject* args, PyObject* kwargs)
20062006 &frame, &callback, &user_data))
20072007 return GetPyNone ();
20082008
2009+ std::lock_guard<std::recursive_mutex> lk (GContext->mutex );
2010+
20092011 if (frame > GContext->callbackRegistry ->highestFrame )
20102012 GContext->callbackRegistry ->highestFrame = frame;
20112013
2012- // TODO: check previous entry and deprecate if existing
2013- Py_XINCREF (callback);
2014-
2015- if (user_data)
2016- Py_XINCREF (user_data);
2017- mvSubmitCallback ([=]()
2018- {
2019- GContext->callbackRegistry ->frameCallbacks [frame] = callback;
2020- GContext->callbackRegistry ->frameCallbacksUserData [frame] = user_data;
2021- });
2014+ GContext->callbackRegistry ->frameCallbacks .insert_or_assign (frame, mvPyObject (callback, true ));
2015+ GContext->callbackRegistry ->frameCallbacksUserData .insert_or_assign (frame, mvPyObject (user_data, true ));
20222016
20232017 return GetPyNone ();
20242018}
@@ -2033,14 +2027,9 @@ set_exit_callback(PyObject* self, PyObject* args, PyObject* kwargs)
20332027 &user_data))
20342028 return GetPyNone ();
20352029
2036- Py_XINCREF (callback);
2037- if (user_data)
2038- Py_XINCREF (user_data);
2039- mvSubmitCallback ([=]()
2040- {
2041- GContext->callbackRegistry ->onCloseCallback = SanitizeCallback (callback);
2042- GContext->callbackRegistry ->onCloseCallbackUserData = user_data;
2043- });
2030+ *GContext->callbackRegistry ->onCloseCallback = mvPyObject (callback == Py_None? nullptr : callback, true );
2031+ *GContext->callbackRegistry ->onCloseCallbackUserData = mvPyObject (user_data, true );
2032+
20442033 return GetPyNone ();
20452034}
20462035
@@ -2054,17 +2043,8 @@ set_viewport_resize_callback(PyObject* self, PyObject* args, PyObject* kwargs)
20542043 &callback, &user_data))
20552044 return GetPyNone ();
20562045
2057- if (callback)
2058- Py_XINCREF (callback);
2059-
2060- if (user_data)
2061- Py_XINCREF (user_data);
2062-
2063- mvSubmitCallback ([=]()
2064- {
2065- GContext->callbackRegistry ->resizeCallback = SanitizeCallback (callback);
2066- GContext->callbackRegistry ->resizeCallbackUserData = user_data;
2067- });
2046+ *GContext->callbackRegistry ->resizeCallback = mvPyObject (callback == Py_None? nullptr : callback, true );
2047+ *GContext->callbackRegistry ->resizeCallbackUserData = mvPyObject (user_data, true );
20682048
20692049 return GetPyNone ();
20702050}
@@ -2527,14 +2507,26 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs)
25272507
25282508 if (filepathLength == 0 && callback) // not specified, return array instead
25292509 {
2530- // Py_XINCREF(callback);
25312510 PyObject* newbuffer = nullptr ;
25322511 PymvBuffer* newbufferview = PyObject_New (PymvBuffer, &PymvBufferType);
25332512 newbuffer = PyObject_Init ((PyObject*)newbufferview, &PymvBufferType);
2534- mvSubmitTask ([newbuffer, callback, newbufferview]() {
2513+
2514+ // Making an owned ref while we're still holding GIL (can't do this within mvSubmitTask).
2515+ auto stored_callback = std::make_shared<mvPyObject>(callback, true );
2516+ // We need to schedule this into the rendering thread because OutputFrameBufferArray
2517+ // accesses the rendering API, which might well have thread-local things in the context.
2518+ mvSubmitTask ([stored_callback, newbuffer, newbufferview]() {
25352519 OutputFrameBufferArray (newbufferview);
2536- mvAddCallback (callback, 0 , newbuffer, nullptr , false );
2537- });
2520+ mvAddOwnerlessCallback (
2521+ stored_callback, std::make_shared<mvPyObject>(nullptr ),
2522+ 0 , " " ,
2523+ // Note: the callback queue will DECREF the value returned by this
2524+ // lambda, effectively deleting `newbuffer` so that we don't need
2525+ // to perform any special cleanup. We just pass the value as it is,
2526+ // keeping a refcount of 1 all the time until the callback is done.
2527+ [=]() { return newbuffer; }
2528+ );
2529+ });
25382530
25392531 return GetPyNone ();
25402532 }
@@ -2571,7 +2563,7 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs)
25712563static PyObject*
25722564is_dearpygui_running (PyObject* self, PyObject* args, PyObject* kwargs)
25732565{
2574- return ToPyBool (GContext->started );
2566+ return ToPyBool (GContext->running );
25752567}
25762568
25772569static PyObject*
@@ -2591,6 +2583,7 @@ setup_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
25912583 while (!GContext->itemRegistry ->containers .empty ())
25922584 GContext->itemRegistry ->containers .pop ();
25932585 GContext->started = true ;
2586+ GContext->running = true ;
25942587 GContext->future = std::async (std::launch::async, []() {return mvRunCallbacks (); });
25952588 Py_END_ALLOW_THREADS;
25962589 return GetPyNone ();
@@ -2646,67 +2639,73 @@ create_context(PyObject* self, PyObject* args, PyObject* kwargs)
26462639static PyObject*
26472640destroy_context (PyObject* self, PyObject* args, PyObject* kwargs)
26482641{
2649- // std::lock_guard<std::recursive_mutex> lk(GContext->mutex);
2650-
2651- Py_BEGIN_ALLOW_THREADS;
2652-
26532642 if (GContext == nullptr )
26542643 {
26552644 assert (false );
26562645 }
26572646
26582647 else
26592648 {
2660- // hacky fix, started was set to false
2661- // to exit the event loop, but needs to be
2662- // true in order to run DPG commands for the
2663- // exit callback.
2664- GContext->started = true ;
2665- mvSubmitCallback ([=]() {
2666- mvRunCallback (GContext->callbackRegistry ->onCloseCallback , 0 , nullptr , GContext->callbackRegistry ->onCloseCallbackUserData );
2667- GContext->started = false ; // return to false after
2668- });
2669-
2670- if (GContext->viewport != nullptr )
2671- mvCleanupViewport (*GContext->viewport );
2672-
2673- ImNodes::DestroyContext ();
2674- ImPlot::DestroyContext ();
2675- ImGui::DestroyContext ();
2649+ // Make sure everyone knows we're shutting down, even if stop_dearpygui
2650+ // was not called.
2651+ StopRendering ();
26762652
2677- mvToolManager::Reset ();
2678- ClearItemRegistry (*GContext->itemRegistry );
2653+ Py_BEGIN_ALLOW_THREADS;
26792654
2655+ // Queue the close callback, if any. The environment is still healthy enough
2656+ // for it to run, except that no more frames will be rendered with the current GContext.
2657+ mvAddOwnerlessCallback (GContext->callbackRegistry ->onCloseCallback , GContext->callbackRegistry ->onCloseCallbackUserData );
26802658
2681-
2682- // #define X(el) el::s_class_theme_component = nullptr; el::s_class_theme_disabled_component = nullptr;
2683- #define X (el ) DearPyGui::GetClassThemeComponent(mvAppItemType::el) = nullptr ; DearPyGui::GetDisabledClassThemeComponent(mvAppItemType::el) = nullptr ;
2684- MV_ITEM_TYPES
2685- #undef X
2686-
2687- mvSubmitCallback ([=]() {
2659+ // Shutting down the callback loop - this will run right after the close callback
2660+ mvSubmitCallback ([]() {
26882661 GContext->callbackRegistry ->running = false ;
2689- });
2662+ }, true );
2663+ // Waiting for it to complete all tasks and shut down
26902664 if (GContext->future .valid ())
26912665 GContext->future .get ();
2692- if (GContext->viewport )
2693- delete GContext->viewport ;
26942666
2695- delete GContext->itemRegistry ;
2696- delete GContext->callbackRegistry ;
2697- delete GContext;
2698- GContext = nullptr ;
2667+ // The rest of cleanup must be done with GIL locked because it might lead
2668+ // to occasional DECREFs on Python objects (e.g. a callback or user_data).
2669+ Py_END_ALLOW_THREADS;
2670+
2671+ mvContext* context_to_delete = nullptr ;
2672+ {
2673+ // Even though the handlers thread is down, there's still a chance that
2674+ // the user calls DPG from another Python thread. We'd better lock the
2675+ // mutex while we're tinkering with all the global structures.
2676+ std::lock_guard<std::recursive_mutex> lk (GContext->mutex );
2677+
2678+ mvToolManager::Reset ();
2679+ ClearItemRegistry (*GContext->itemRegistry );
2680+
2681+ ImNodes::DestroyContext ();
2682+ ImPlot::DestroyContext ();
2683+ ImGui::DestroyContext ();
2684+
2685+ #define X (el ) DearPyGui::GetClassThemeComponent(mvAppItemType::el) = nullptr ; DearPyGui::GetDisabledClassThemeComponent(mvAppItemType::el) = nullptr ;
2686+ MV_ITEM_TYPES
2687+ X (All)
2688+ #undef X
2689+
2690+ if (GContext->viewport )
2691+ delete GContext->viewport ;
2692+
2693+ delete GContext->itemRegistry ;
2694+ delete GContext->callbackRegistry ;
2695+ context_to_delete = GContext;
2696+ GContext = nullptr ;
2697+ }
2698+ delete context_to_delete;
26992699 }
2700- Py_END_ALLOW_THREADS;
27012700
27022701 return GetPyNone ();
27032702}
27042703
27052704static PyObject*
27062705stop_dearpygui (PyObject* self, PyObject* args, PyObject* kwargs)
27072706{
2708- std::lock_guard<std::recursive_mutex> lk (GContext-> mutex );
2709- GContext->started = false ;
2707+ StopRendering ( );
2708+ std::lock_guard<std::recursive_mutex> lk ( GContext->mutex ) ;
27102709 auto viewport = GContext->viewport ;
27112710 if (viewport)
27122711 viewport->running = false ;
@@ -3771,37 +3770,17 @@ get_item_configuration(PyObject* self, PyObject* args, PyObject* kwargs)
37713770 PyDict_SetItemString (pdict, " height" , py_height);
37723771 PyDict_SetItemString (pdict, " indent" , py_indent);
37733772
3774- if (appitem->config .callback )
3775- {
3776- Py_XINCREF (appitem->config .callback );
3777- PyDict_SetItemString (pdict, " callback" , appitem->config .callback );
3778- }
3779- else
3780- PyDict_SetItemString (pdict, " callback" , GetPyNone ());
3773+ PyObject* callback = appitem->config .callback ;
3774+ PyDict_SetItemString (pdict, " callback" , callback? callback : Py_None);
37813775
3782- if (appitem->config .dropCallback )
3783- {
3784- Py_XINCREF (appitem->config .dropCallback );
3785- PyDict_SetItemString (pdict, " drop_callback" , appitem->config .dropCallback );
3786- }
3787- else
3788- PyDict_SetItemString (pdict, " drop_callback" , GetPyNone ());
3776+ PyObject* dropCallback = appitem->config .dropCallback ;
3777+ PyDict_SetItemString (pdict, " drop_callback" , dropCallback? dropCallback : Py_None);
37893778
3790- if (appitem->config .dragCallback )
3791- {
3792- Py_XINCREF (appitem->config .dragCallback );
3793- PyDict_SetItemString (pdict, " drag_callback" , appitem->config .dragCallback );
3794- }
3795- else
3796- PyDict_SetItemString (pdict, " drag_callback" , GetPyNone ());
3779+ PyObject* dragCallback = appitem->config .dragCallback ;
3780+ PyDict_SetItemString (pdict, " drag_callback" , dragCallback? dragCallback : Py_None);
37973781
3798- if (appitem->config .user_data )
3799- {
3800- Py_XINCREF (appitem->config .user_data );
3801- PyDict_SetItemString (pdict, " user_data" , appitem->config .user_data );
3802- }
3803- else
3804- PyDict_SetItemString (pdict, " user_data" , GetPyNone ());
3782+ PyObject* user_data = *(appitem->config .user_data );
3783+ PyDict_SetItemString (pdict, " user_data" , user_data? user_data : Py_None);
38053784
38063785 appitem->getSpecificConfiguration (pdict);
38073786 }
@@ -4229,21 +4208,8 @@ capture_next_item(PyObject* self, PyObject* args, PyObject* kwargs)
42294208
42304209 std::lock_guard<std::recursive_mutex> lk (GContext->mutex );
42314210
4232- if (GContext->itemRegistry ->captureCallback )
4233- Py_XDECREF (GContext->itemRegistry ->captureCallback );
4234-
4235- if (GContext->itemRegistry ->captureCallbackUserData )
4236- Py_XDECREF (GContext->itemRegistry ->captureCallbackUserData );
4237-
4238- Py_XINCREF (callable);
4239- if (user_data)
4240- Py_XINCREF (user_data);
4241- if (callable == Py_None)
4242- GContext->itemRegistry ->captureCallback = nullptr ;
4243- else
4244- GContext->itemRegistry ->captureCallback = callable;
4245-
4246- GContext->itemRegistry ->captureCallbackUserData = user_data;
4211+ GContext->itemRegistry ->captureCallback = mvPyObject (callable == Py_None? nullptr : callable, true );
4212+ GContext->itemRegistry ->captureCallbackUserData = mvPyObject (user_data, true );
42474213
42484214 return GetPyNone ();
42494215}
@@ -4254,29 +4220,44 @@ get_callback_queue(PyObject* self, PyObject* args, PyObject* kwargs)
42544220 if (GContext->callbackRegistry ->jobs .empty ())
42554221 return GetPyNone ();
42564222
4223+ std::lock_guard<std::recursive_mutex> lk (GContext->mutex );
4224+
42574225 PyObject* pArgs = PyTuple_New (GContext->callbackRegistry ->jobs .size ());
42584226 for (int i = 0 ; i < GContext->callbackRegistry ->jobs .size (); i++)
42594227 {
42604228 PyObject* job = PyTuple_New (4 );
4261- if (GContext->callbackRegistry ->jobs [i].callback )
4262- PyTuple_SetItem (job, 0 , GContext->callbackRegistry ->jobs [i].callback );
4263- else
4264- PyTuple_SetItem (job, 0 , GetPyNone ());
4229+ const mvCallbackJob& cur_entry = GContext->callbackRegistry ->jobs [i];
42654230
4266- if (GContext->callbackRegistry ->jobs [i].sender == 0 )
4267- PyTuple_SetItem (job, 1 , ToPyString (GContext->callbackRegistry ->jobs [i].sender_str ));
4231+ PyObject* callback;
4232+ if (cur_entry.ownerless_callback )
4233+ {
4234+ callback = *cur_entry.ownerless_callback ;
4235+ Py_XINCREF (callback);
4236+ }
42684237 else
4269- PyTuple_SetItem (job, 1 , ToPyUUID (GContext->callbackRegistry ->jobs [i].sender ));
4238+ {
4239+ auto liveOwner = cur_entry.owner .lock ();
4240+ // If the owner of this entry is gone, we'll just set the callback to None.
4241+ // This lets us create the output list right away, without the need to collect
4242+ // valid callbacks first. Also, this mimicks the behavior of widgets without
4243+ // a `callback` set on them, which in the "manual" mode put null callbacks into
4244+ // the queue. It's mostly a debug/diagnostic mode anyway - captures everything.
4245+ callback = liveOwner? cur_entry.callback : nullptr ;
4246+ // Must only be done while we own liveOwner.
4247+ Py_XINCREF (callback);
4248+ }
4249+ PyTuple_SetItem (job, 0 , callback? callback : GetPyNone ());
42704250
4271- if (GContext->callbackRegistry ->jobs [i].app_data )
4272- PyTuple_SetItem (job, 2 , GContext->callbackRegistry ->jobs [i].app_data ); // steals data, so don't deref
4273- else
4274- PyTuple_SetItem (job, 2 , GetPyNone ());
4251+ PyTuple_SetItem (job, 1 , ToPyUUID (cur_entry.sender , cur_entry.alias ));
42754252
4276- if (GContext->callbackRegistry ->jobs [i].user_data )
4277- PyTuple_SetItem (job, 3 , GContext->callbackRegistry ->jobs [i].user_data ); // steals data, so don't deref
4278- else
4279- PyTuple_SetItem (job, 3 , GetPyNone ());
4253+ // app_data_func() returns a new PyObject reference (passing ownership to us),
4254+ // therefore we don't need to INCREF it.
4255+ PyObject* app_data = cur_entry.app_data_func ();
4256+ PyTuple_SetItem (job, 2 , app_data? app_data : GetPyNone ());
4257+
4258+ PyObject* user_data = *cur_entry.user_data ;
4259+ Py_XINCREF (user_data);
4260+ PyTuple_SetItem (job, 3 , user_data? user_data : GetPyNone ());
42804261
42814262 PyTuple_SetItem (pArgs, i, job);
42824263 }
0 commit comments