Fix #129323: Slowdown adding/removing objects from Python

Python scripts could perform actions that created notifiers
which would not be handled until the script was complete.

In the case of adding & removing objects a notifier would be created
for adding the object, then cleared when the ID was removed.

This lead to the notifier queue filling up with cleared notifiers
which were included in the search whenever an ID was removed.

The result of this was that adding and removing objects from a script
would become increasingly slower & use more memory.

Resolve by storing the current notifier being handed which isn't freed
(only cleared). The notifier handling loop detects cleared notifiers
and frees them after use.
This commit is contained in:
Campbell Barton
2024-10-24 12:50:31 +11:00
parent 9a252c2e73
commit f23478439c
3 changed files with 47 additions and 5 deletions

View File

@@ -33,6 +33,7 @@ typedef struct WindowManagerRuntimeHandle WindowManagerRuntimeHandle;
/* Defined here: */
struct wmNotifier;
struct wmWindow;
struct wmWindowManager;
@@ -182,7 +183,9 @@ typedef struct wmWindowManager {
* \note keep in sync with `notifier_queue` adding/removing elements must also update this set.
*/
struct GSet *notifier_queue_set;
void *_pad1;
/** The current notifier in the `notifier_queue` being handled (clear instead of freeing). */
const struct wmNotifier *notifier_current;
/** Available/pending extensions updates. */
int extensions_updates;

View File

@@ -210,6 +210,7 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id)
BLI_listbase_clear(&wm->paintcursors);
BLI_listbase_clear(&wm->notifier_queue);
wm->notifier_queue_set = nullptr;
wm->notifier_current = nullptr;
BLI_listbase_clear(&wm->keyconfigs);
wm->defaultconf = nullptr;
@@ -591,6 +592,8 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm)
BLI_gset_free(wm->notifier_queue_set, nullptr);
wm->notifier_queue_set = nullptr;
}
BLI_assert(wm->notifier_current == nullptr);
wm->notifier_current = nullptr;
if (wm->message_bus != nullptr) {
WM_msgbus_destroy(wm->message_bus);

View File

@@ -410,9 +410,20 @@ void WM_main_remove_notifier_reference(const void *reference)
const bool removed = BLI_gset_remove(wm->notifier_queue_set, note, nullptr);
BLI_assert(removed);
UNUSED_VARS_NDEBUG(removed);
/* Don't remove because this causes problems for #wm_event_do_notifiers
* which may be looping on the data (deleting screens). */
wm_notifier_clear(note);
/* Remove unless this is being iterated over by the caller.
* This is done to prevent `wm->notifier_queue` accumulating notifiers
* that aren't handled which can happen when notifiers are added from Python scripts.
* see #129323. */
if (wm->notifier_current == note) {
/* Don't remove because this causes problems for #wm_event_do_notifiers
* which may be looping on the data (deleting screens). */
wm_notifier_clear(note);
}
else {
BLI_remlink(&wm->notifier_queue, note);
MEM_freeN(note);
}
}
}
@@ -570,7 +581,20 @@ void wm_event_do_notifiers(bContext *C)
CTX_wm_window_set(C, win);
LISTBASE_FOREACH_MUTABLE (const wmNotifier *, note, &wm->notifier_queue) {
BLI_assert(wm->notifier_current == nullptr);
for (const wmNotifier *note = static_cast<const wmNotifier *>(wm->notifier_queue.first),
*note_next = nullptr;
note;
note = note_next)
{
if (wm_notifier_is_clear(note)) {
note_next = note->next;
MEM_freeN((void *)note);
continue;
}
wm->notifier_current = note;
if (note->category == NC_WM) {
if (ELEM(note->data, ND_FILEREAD, ND_FILESAVE)) {
wm->file_saved = 1;
@@ -640,6 +664,14 @@ void wm_event_do_notifiers(bContext *C)
if (ELEM(note->category, NC_SCENE, NC_OBJECT, NC_GEOM, NC_WM)) {
clear_info_stats = true;
}
wm->notifier_current = nullptr;
note_next = note->next;
if (wm_notifier_is_clear(note)) {
BLI_remlink(&wm->notifier_queue, (void *)note);
MEM_freeN((void *)note);
}
}
if (clear_info_stats) {
@@ -662,6 +694,8 @@ void wm_event_do_notifiers(bContext *C)
}
}
BLI_assert(wm->notifier_current == nullptr);
/* The notifiers are sent without context, to keep it clean. */
while (
const wmNotifier *note = static_cast<const wmNotifier *>(BLI_pophead(&wm->notifier_queue)))
@@ -670,6 +704,8 @@ void wm_event_do_notifiers(bContext *C)
MEM_freeN((void *)note);
continue;
}
/* NOTE: no need to set `wm->notifier_current` since it's been removed from the queue. */
const bool removed = BLI_gset_remove(wm->notifier_queue_set, note, nullptr);
BLI_assert(removed);
UNUSED_VARS_NDEBUG(removed);