Screen: support garbage collection of layout panel states
Identifying layout-panels using strings and storing their open-state in their parent panel is generally very convenient. However, right now there is no mechanism to free the open-close state of panels that are not in use anymore. It's generally not possible to know if a panel is not used anymore (e.g. it may also belong to a temporarily disabled add-on). So every mechanism here will be based on heuristics. A simple but typically very good heuristic is to just remove the least-recently-used panel states when there are too many. So that's what is implemented here. This introduces a logical clock that increases whenever a panel state is used and stores the last usage time on the panel state. This allows us to remove the least-recently-used states later on. Specifically, that is done when the .blend file is loaded the next time. It's not done at another time, because it quite difficult to be sure that freeing panel states wouldn't cause dangling pointers in such cases. I think doing it on-load should be good enough for all practical use-cases. Note, I don't know if the lack of such garbage collection has ever caused a problem anywhere. However, it seems better to have some solution now than trying to solve it after the problem has occurred and made someone's file unusable. Pull Request: https://projects.blender.org/blender/blender/pulls/135569
This commit is contained in:
@@ -329,6 +329,7 @@ static void panel_list_copy(ListBase *newlb, const ListBase *lb)
|
||||
new_panel->drawname = nullptr;
|
||||
|
||||
BLI_listbase_clear(&new_panel->layout_panel_states);
|
||||
new_panel->layout_panel_states_clock = old_panel->layout_panel_states_clock;
|
||||
LISTBASE_FOREACH (LayoutPanelState *, src_state, &old_panel->layout_panel_states) {
|
||||
LayoutPanelState *new_state = MEM_dupallocN<LayoutPanelState>(__func__, *src_state);
|
||||
new_state->idname = BLI_strdup(src_state->idname);
|
||||
@@ -514,14 +515,24 @@ LayoutPanelState *BKE_panel_layout_panel_state_ensure(Panel *panel,
|
||||
const StringRef idname,
|
||||
const bool default_closed)
|
||||
{
|
||||
const uint32_t logical_time = ++panel->layout_panel_states_clock;
|
||||
/* Overflow happened, reset all last used times. Not sure if this will ever happen in practice,
|
||||
* but better handle the overflow explicitly. */
|
||||
if (logical_time == 0) {
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
state->last_used = 0;
|
||||
}
|
||||
}
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
if (state->idname == idname) {
|
||||
state->last_used = logical_time;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
LayoutPanelState *state = MEM_callocN<LayoutPanelState>(__func__);
|
||||
state->idname = BLI_strdupn(idname.data(), idname.size());
|
||||
SET_FLAG_FROM_TEST(state->flag, !default_closed, LAYOUT_PANEL_STATE_FLAG_OPEN);
|
||||
state->last_used = logical_time;
|
||||
BLI_addtail(&panel->layout_panel_states, state);
|
||||
return state;
|
||||
}
|
||||
@@ -537,15 +548,21 @@ Panel *BKE_panel_new(PanelType *panel_type)
|
||||
return panel;
|
||||
}
|
||||
|
||||
static void layout_panel_state_delete(LayoutPanelState *state)
|
||||
{
|
||||
MEM_freeN(state->idname);
|
||||
MEM_freeN(state);
|
||||
}
|
||||
|
||||
void BKE_panel_free(Panel *panel)
|
||||
{
|
||||
MEM_SAFE_FREE(panel->activedata);
|
||||
MEM_SAFE_FREE(panel->drawname);
|
||||
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
MEM_freeN(state->idname);
|
||||
LISTBASE_FOREACH_MUTABLE (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
BLI_remlink(&panel->layout_panel_states, state);
|
||||
layout_panel_state_delete(state);
|
||||
}
|
||||
BLI_freelistN(&panel->layout_panel_states);
|
||||
|
||||
MEM_delete(panel->runtime);
|
||||
MEM_freeN(panel);
|
||||
@@ -1178,6 +1195,26 @@ void BKE_screen_area_map_blend_write(BlendWriter *writer, ScrAreaMap *area_map)
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_least_recently_used_panel_states(Panel &panel, const int64_t max_kept)
|
||||
{
|
||||
Vector<LayoutPanelState *, 1024> all_states;
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel.layout_panel_states) {
|
||||
all_states.append(state);
|
||||
}
|
||||
if (all_states.size() <= max_kept) {
|
||||
return;
|
||||
}
|
||||
std::sort(all_states.begin(),
|
||||
all_states.end(),
|
||||
[](const LayoutPanelState *a, const LayoutPanelState *b) {
|
||||
return a->last_used < b->last_used;
|
||||
});
|
||||
for (LayoutPanelState *state : all_states.as_span().drop_back(max_kept)) {
|
||||
BLI_remlink(&panel.layout_panel_states, state);
|
||||
layout_panel_state_delete(state);
|
||||
}
|
||||
}
|
||||
|
||||
static void direct_link_panel_list(BlendDataReader *reader, ListBase *lb)
|
||||
{
|
||||
BLO_read_struct_list(reader, Panel, lb);
|
||||
@@ -1192,6 +1229,11 @@ static void direct_link_panel_list(BlendDataReader *reader, ListBase *lb)
|
||||
LISTBASE_FOREACH (LayoutPanelState *, state, &panel->layout_panel_states) {
|
||||
BLO_read_string(reader, &state->idname);
|
||||
}
|
||||
/* Reduce the number of panel states to a reasonable number. This avoids the list getting
|
||||
* arbitrarily large over time. Ideally this could be done more eagerly and not only when
|
||||
* loading the file. However, it's hard to make sure that no other code is currently
|
||||
* referencing the panel states in other cases. */
|
||||
remove_least_recently_used_panel_states(*panel, 200);
|
||||
direct_link_panel_list(reader, &panel->children);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,12 @@ typedef struct LayoutPanelState {
|
||||
/** Identifier of the panel. */
|
||||
char *idname;
|
||||
uint8_t flag;
|
||||
char _pad[7];
|
||||
char _pad[3];
|
||||
/**
|
||||
* A logical time set from #layout_panel_states_clock when the panel is used by the UI. This is
|
||||
* used to detect the least-recently-used panel states when some panel states should be removed.
|
||||
*/
|
||||
uint32_t last_used;
|
||||
} LayoutPanelState;
|
||||
|
||||
enum LayoutPanelStateFlag {
|
||||
@@ -180,6 +185,13 @@ typedef struct Panel {
|
||||
* `layout.panel(...)` in Python. For more information on layout-panels, see `uiLayoutPanelProp`.
|
||||
*/
|
||||
ListBase layout_panel_states;
|
||||
/**
|
||||
* This is increased whenever a layout panel state is used by the UI. This is used to allow for
|
||||
* some garbage collection of panel states when #layout_panel_states becomes large. It works by
|
||||
* removing all least-recently-used panel states up to a certain threshold.
|
||||
*/
|
||||
uint32_t layout_panel_states_clock;
|
||||
char _pad2[4];
|
||||
|
||||
struct Panel_Runtime *runtime;
|
||||
} Panel;
|
||||
|
||||
Reference in New Issue
Block a user