retain box state cache within ui core

This commit is contained in:
jacob 2025-12-19 15:07:04 -06:00
parent ad56fafeff
commit fbf7882db2
7 changed files with 615 additions and 424 deletions

View File

@ -277,7 +277,7 @@ Inline void RemoveDictEntry(Dict *dict, DictEntry *entry)
dict->last = prev;
}
}
/* Insert into free list */
/* Add to free list */
{
entry->next = dict->first_free;
dict->first_free = entry;

View File

@ -31,6 +31,8 @@ u64 GC_HashFromGlyphDesc(GC_GlyphDesc desc)
GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size)
{
GC_Run result = Zi;
if (str.len > 0)
{
TempArena scratch = BeginScratch(arena);
Arena *perm = PermArena();
@ -208,10 +210,12 @@ GC_Run GC_RunFromString(Arena *arena, String str, GC_FontKey font, f32 font_size
result.font_cap = glyph->font_cap;
}
EndScratch(scratch);
}
// result.ready = uncached_codepoints_count == 0 && pending_glyphs_count == 0;
result.ready = 1;
EndScratch(scratch);
return result;
}

View File

@ -286,7 +286,7 @@ N_MsgAssembler *N_AcquireMsgAssembler(N_Channel *channel, u64 msg_id, u64 chunk_
/* FIXME: Ensure chunk_count > 0 */
ma->is_reliable = is_reliable;
/* Insert into channel list */
/* Add to channel list */
ma->touched_ns = now_ns;
if (channel->most_recent_msg_assembler)
{
@ -299,7 +299,7 @@ N_MsgAssembler *N_AcquireMsgAssembler(N_Channel *channel, u64 msg_id, u64 chunk_
}
channel->most_recent_msg_assembler = ma;
/* Insert into lookup table */
/* Add to lookup table */
u64 hash = N_HashFromMsg(channel->id, msg_id);
ma->hash = hash;
N_MsgAssemblerLookupBin *bin = &host->msg_assembler_lookup_bins[hash % host->num_msg_assembler_lookup_bins];

View File

@ -19,7 +19,12 @@ void V_Shutdown(void)
V_Frame *V_CurrentFrame(void)
{
return &V.frames[V.current_frame_idx];
return &V.frames[V.current_frame_tick % countof(V.frames)];
}
V_Frame *V_LastFrame(void)
{
return &V.frames[(V.current_frame_tick - 1) % countof(V.frames)];
}
S_Cmd *V_PushSimCmd(S_CmdKind kind)
@ -421,19 +426,13 @@ void V_TickForever(WaveLaneCtx *lane)
b32 shutdown = 0;
while (!shutdown)
{
u64 last_frame_idx = V.current_frame_idx;
u64 frame_idx = last_frame_idx + 1;
if (frame_idx >= countof(V.frames))
{
frame_idx = 0;
}
V.current_frame_idx = frame_idx;
V_Frame *last_frame = &V.frames[last_frame_idx];
V_Frame *frame = &V.frames[frame_idx];
//////////////////////////////
//- Begin frame
V.current_frame_tick += 1;
V_Frame *last_frame = V_LastFrame();
V_Frame *frame = V_CurrentFrame();
{
Arena *old_arena = frame->arena;
Arena *old_dverts_arena = frame->dverts_arena;
@ -462,8 +461,8 @@ void V_TickForever(WaveLaneCtx *lane)
frame->edit_camera_zoom = last_frame->edit_camera_zoom;
frame->look = last_frame->look;
frame->tick = V.current_frame_tick;
frame->time_ns = TimeNs();
frame->tick = last_frame->tick + 1;
frame->dt_ns = frame->time_ns - last_frame->time_ns;
frame->dt = SecondsFromNs(frame->dt_ns);
@ -790,20 +789,29 @@ void V_TickForever(WaveLaneCtx *lane)
/* TODO: Remove this (testing) */
//- Init test layout
if (!V.root_space)
if (!V.root_panel)
{
V_Space *space = PushStruct(perm, V_Space);
space->axis = Axis_X;
V.root_space = space;
space->pref_size[Axis_X] = UI_GROW(1, 0);
space->pref_size[Axis_Y] = UI_GROW(1, 0);
{
V_Panel *panel = PushStruct(perm, V_Panel);
panel->axis = Axis_X;
// panel->pref_size[Axis_X] = UI_PIX(frame->ui_dims.x, 0);
// panel->pref_size[Axis_Y] = UI_PIX(frame->ui_dims.y, 0);
panel->key = UI_TransKey(); /* TODO: Don't use transient keys for panels */
panel->pref_size[Axis_X] = UI_GROW(1, 0);
panel->pref_size[Axis_Y] = UI_GROW(1, 0);
panel->is_organizing_panel = 1;
V.root_panel = panel;
}
{
V_Panel *panel = PushStruct(perm, V_Panel);
panel->space = space;
DllQueuePushNP(space->first_panel, space->last_panel, panel, next_in_space, prev_in_space);
++space->panels_count;
panel->parent = V.root_panel;
panel->axis = !panel->parent->axis;
panel->key = UI_TransKey(); /* TODO: Don't use transient keys for panels */
panel->pref_size[Axis_X] = UI_GROW(1, 0);
panel->pref_size[Axis_Y] = UI_GROW(1, 0);
DllQueuePush(panel->parent->first, panel->parent->last, panel);
++panel->parent->count;
{
V_Window *window = PushStruct(perm, V_Window);
@ -824,9 +832,13 @@ void V_TickForever(WaveLaneCtx *lane)
{
V_Panel *panel = PushStruct(perm, V_Panel);
panel->space = space;
DllQueuePushNP(space->first_panel, space->last_panel, panel, next_in_space, prev_in_space);
++space->panels_count;
panel->parent = V.root_panel;
panel->axis = !panel->parent->axis;
panel->key = UI_TransKey(); /* TODO: Don't use transient keys for panels */
panel->pref_size[Axis_X] = UI_GROW(1, 0);
panel->pref_size[Axis_Y] = UI_GROW(1, 0);
DllQueuePush(panel->parent->first, panel->parent->last, panel);
++panel->parent->count;
{
V_Window *window = PushStruct(perm, V_Window);
@ -837,6 +849,29 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
// {
// V_Space *space = PushStruct(perm, V_Space);
// space->parent = V.root_space;
// space->axis = !space->parent->axis;
// space->pref_size[Axis_X] = UI_GROW(1, 0);
// space->pref_size[Axis_Y] = UI_GROW(1, 0);
// DllQueuePush(space->parent->first, space->parent->last, space);
// ++space->parent->count;
// V_Panel *panel = PushStruct(perm, V_Panel);
// panel->space = space;
// DllQueuePushNP(space->first_panel, space->last_panel, panel, next_in_space, prev_in_space);
// ++space->panels_count;
// {
// V_Window *window = PushStruct(perm, V_Window);
// window->panel = panel;
// window->is_viewport_window = 1;
// DllQueuePushNP(panel->first_window, panel->last_window, window, next_in_panel, prev_in_panel);
// ++panel->windows_count;
// }
// }
// {
// V_Panel *panel = PushStruct(perm, V_Panel);
// panel->space = space;
@ -855,55 +890,53 @@ void V_TickForever(WaveLaneCtx *lane)
if (frame->is_editing)
{
Struct(SpaceDfsNode) { SpaceDfsNode *next; b32 visited; V_Space *space; UI_Checkpoint cp; };
SpaceDfsNode *first_space_dfs = PushStruct(frame->arena, SpaceDfsNode);
first_space_dfs->space = V.root_space;
Struct(PanelDfsNode) { PanelDfsNode *next; b32 visited; V_Panel *panel; UI_Checkpoint cp; };
PanelDfsNode *first_panel_dfs = PushStruct(frame->arena, PanelDfsNode);
first_panel_dfs->panel = V.root_panel;
//- Iterate spaces
while (first_space_dfs)
//- Iterate panels
while (first_panel_dfs)
{
SpaceDfsNode *space_dfs = first_space_dfs;
V_Space *space = space_dfs->space;
if (!space_dfs->visited)
PanelDfsNode *panel_dfs = first_panel_dfs;
V_Panel *panel = panel_dfs->panel;
if (!panel_dfs->visited)
{
space_dfs->visited = 1;
for (V_Space *child = space->last; child; child = child->prev)
panel_dfs->visited = 1;
for (V_Panel *child = panel->last; child; child = child->prev)
{
SpaceDfsNode *n = PushStruct(frame->arena, SpaceDfsNode);
n->space = child;
SllStackPush(first_space_dfs, n);
PanelDfsNode *n = PushStruct(frame->arena, PanelDfsNode);
n->panel = child;
SllStackPush(first_panel_dfs, n);
}
UI_Key space_key = UI_TransKey();
UI_SetNext(Width, space->pref_size[Axis_X]);
UI_SetNext(Width, space->pref_size[Axis_Y]);
if (space->axis == Axis_X)
UI_SetNext(Width, panel->pref_size[Axis_X]);
UI_SetNext(Height, panel->pref_size[Axis_Y]);
if (panel->axis == Axis_X)
{
UI_BuildRowEx(space_key);
UI_BuildRowEx(panel->key);
}
else
{
UI_BuildColumnEx(space_key);
UI_BuildColumnEx(panel->key);
}
space_dfs->cp = UI_PushCP(space_key);
panel_dfs->cp = UI_PushCP(panel->key);
//- Build space panels
for (V_Panel *panel = space->first_panel; panel; panel = panel->next_in_space)
if (!panel->is_organizing_panel)
{
// UI_SetNext(Width, UI_SHRINK(0, 1));
UI_SetNext(Width, UI_GROW(1, 0));
UI_SetNext(Height, UI_GROW(1, 0));
// UI_SetNext(Width, UI_GROW(1, 0));
// UI_SetNext(Height, UI_GROW(1, 0));
UI_PushCP(UI_BuildColumn());
{
i64 active_window_idx = ClampI64(panel->active_window_idx, 0, MaxI64(panel->windows_count - 1, 0));
//- Build panel window tabs
//- Build window tabs
f32 tab_spacing = 10;
V_Window *active_window = 0;
{
UI_SetNext(Width, UI_SHRINK(0, 0));
UI_SetNext(Height, UI_SHRINK(0, 0));
UI_PushCP(UI_BuildRow());
i64 window_idx = 0;
UI_SetNext(Width, UI_SHRINK(0, 1));
UI_SetNext(Height, UI_SHRINK(0, 1));
UI_PushCP(UI_BuildRow());
for (V_Window *window = panel->first_window; window; window = window->next_in_panel)
{
if (!window->is_viewport_window)
@ -920,8 +953,8 @@ void V_TickForever(WaveLaneCtx *lane)
}
UI_SetNext(BackgroundColor, VEC4(0.2, 0.2, 0.2, 1));
UI_SetNext(Border, 1);
UI_SetNext(Width, UI_SHRINK(theme.text_padding_x, 0));
UI_SetNext(Height, UI_SHRINK(theme.text_padding_y, 0));
UI_SetNext(Width, UI_SHRINK(theme.text_padding_x, 1));
UI_SetNext(Height, UI_SHRINK(theme.text_padding_y, 1));
UI_SetNext(ChildAlignment, UI_Alignment_Center);
UI_PushCP(UI_BuildRow());
{
@ -940,7 +973,7 @@ void V_TickForever(WaveLaneCtx *lane)
}
UI_PopCP(UI_TopCP());
}
//- Build active panel active_window
//- Build active window
if (active_window && !active_window->is_viewport_window)
{
V_Window *window = active_window;
@ -968,13 +1001,24 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
UI_PopCP(UI_TopCP());
}
}
else
{
UI_PopCP(space_dfs->cp);
SllStackPop(first_space_dfs);
UI_PopCP(panel_dfs->cp);
SllStackPop(first_panel_dfs);
//- Build divider
if (panel->next != 0)
{
UI_Key divider_key = UI_TransKey();
UI_SetNext(BackgroundColor, Color_Cyan);
// UI_SetNext(Border, 2);
UI_SetNext(AxisSize, UI_GROW(1, 1), .axis = panel->axis);
UI_SetNext(AxisSize, UI_PIX(5, 1), .axis = !panel->axis);
/* FIXME: Non-transient key */
UI_BuildBoxEx(divider_key);
}
}
}
}

View File

@ -149,28 +149,21 @@ Struct(V_CommandsWidget)
/* TODO: Move boolean fields into bitwise property flags */
Struct(V_Space)
{
V_Space *parent;
V_Space *next;
V_Space *prev;
V_Space *first;
V_Space *last;
i64 panels_count;
struct V_Panel *first_panel;
struct V_Panel *last_panel;
UI_Size pref_size[Axis_CountXY];
Axis axis;
};
Struct(V_Panel)
{
V_Space *space;
V_Panel *next_in_space;
V_Panel *prev_in_space;
V_Panel *parent;
V_Panel *next;
V_Panel *prev;
V_Panel *first;
V_Panel *last;
i64 count;
UI_Key key;
UI_Size pref_size[Axis_CountXY];
Axis axis;
b32 is_organizing_panel;
i64 active_window_idx;
i64 windows_count;
@ -277,12 +270,12 @@ Struct(V_Ctx)
S_Lookup lookup;
S_Key player_key;
V_Space *root_space;
V_Panel *root_panel;
Atomic32 shutdown;
Fence shutdown_complete;
u64 current_frame_idx;
i64 current_frame_tick;
V_Frame frames[2];
};
@ -298,6 +291,7 @@ void V_Shutdown(void);
//~ Helpers
V_Frame *V_CurrentFrame(void);
V_Frame *V_LastFrame(void);
S_Cmd *V_PushSimCmd(S_CmdKind kind);
String V_StringFromHotkey(Arena *arena, V_Hotkey hotkey);

View File

@ -1,4 +1,4 @@
UI_State UI_state = Zi;
UI_Ctx UI = Zi;
////////////////////////////////////////////////////////////
//~ Bootstrap
@ -18,6 +18,11 @@ GC_FontKey UI_GetDefaultFont(void)
////////////////////////////////////////////////////////////
//~ Key helpers
b32 UI_MatchKey(UI_Key a, UI_Key b)
{
return a.hash == b.hash;
}
UI_Key UI_KeyFromString(String str)
{
u64 top_tag = UI_UseTop(Tag);
@ -43,8 +48,7 @@ UI_Key UI_KeyF_(String fmt, ...)
UI_Key UI_TransKey(void)
{
UI_Frame *frame = UI_CurrentFrame();
u64 seed = ++frame->transient_key_seed;
u64 seed = ++UI.transient_key_seed;
UI_Key key = Zi;
key.hash = RandU64FromSeed(seed);
return key;
@ -52,16 +56,14 @@ UI_Key UI_TransKey(void)
UI_Box *UI_BoxFromKey(UI_Key key)
{
UI_State *g = &UI_state;
UI_Box *box = 0;
if (key.hash != 0)
{
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
for (UI_Box *tmp = bin->first; tmp; tmp = tmp->next_in_bin)
UI_BoxBin *bin = &UI.box_bins[key.hash % countof(UI.box_bins)];
for (box = bin->first; box; box = box->next_in_bin)
{
if (tmp->key.hash == key.hash)
if (UI_MatchKey(box->key, key))
{
box = tmp;
break;
}
}
@ -69,6 +71,105 @@ UI_Box *UI_BoxFromKey(UI_Key key)
return box;
}
////////////////////////////////////////////////////////////
//~ Iteration helpers
UI_BoxIterResult UI_FirstBox(Arena *arena, UI_BoxIter *iter, UI_Key start_key)
{
/* Free old stack */
if (iter->first)
{
if (iter->last_free)
{
iter->last_free->next = iter->first;
}
else
{
iter->first_free = iter->first;
}
iter->last_free = iter->first;
iter->first = 0;
}
/* Create root dfs node */
UI_BoxIterDfsNode *dfs = iter->first_free;
if (dfs)
{
SllQueuePop(iter->first_free, iter->last_free);
ZeroStruct(dfs);
}
else
{
dfs = PushStruct(arena, UI_BoxIterDfsNode);
}
dfs->box = UI_BoxFromKey(start_key);
iter->first = dfs;
return UI_NextBox(arena, iter);
}
UI_BoxIterResult UI_NextBox(Arena *arena, UI_BoxIter *iter)
{
UI_BoxIterResult result = Zi;
if (iter->first)
{
UI_BoxIterDfsNode *dfs = iter->first;
UI_Box *box = dfs->box;
if (!dfs->visited)
{
/* Pre order */
dfs->visited = 1;
result.box = box;
result.pre = 1;
/* Push floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
UI_BoxIterDfsNode *child_dfs = iter->first_free;
if (child_dfs)
{
SllQueuePop(iter->first_free, iter->last_free);
ZeroStruct(child_dfs);
}
else
{
child_dfs = PushStruct(arena, UI_BoxIterDfsNode);
}
child_dfs->box = child;
SllStackPush(iter->first, child_dfs);
}
}
/* Push non-floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
UI_BoxIterDfsNode *child_dfs = iter->first_free;
if (child_dfs)
{
SllQueuePop(iter->first_free, iter->last_free);
ZeroStruct(child_dfs);
}
else
{
child_dfs = PushStruct(arena, UI_BoxIterDfsNode);
}
child_dfs->box = child;
SllStackPush(iter->first, child_dfs);
}
}
}
else
{
/* Post order */
result.box = box;
result.pre = 0;
SllStackPop(iter->first);
SllQueuePush(iter->first_free, iter->last_free, dfs);
}
}
return result;
}
////////////////////////////////////////////////////////////
//~ String helpers
@ -410,7 +511,6 @@ UI_Key UI_BuildBoxEx(UI_Key key)
return key;
}
void UI_SetRawTexture(UI_Key key, G_Texture2DRef tex, Rng2 uv)
{
UI_Frame *frame = UI_CurrentFrame();
@ -430,7 +530,6 @@ void UI_SetRawTexture(UI_Key key, G_Texture2DRef tex, Rng2 uv)
UI_Report UI_ReportFromKey(UI_Key key)
{
UI_State *g = &UI_state;
UI_Report result = Zi;
UI_Box *box = UI_BoxFromKey(key);
@ -447,38 +546,39 @@ UI_Report UI_ReportFromKey(UI_Key key)
UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
{
UI_State *g = &UI_state;
//////////////////////////////
//- Init persistent state
if (!g->box_arena)
if (!UI.box_arena)
{
g->box_arena = AcquireArena(Gibi(64));
g->box_bins = PushStructs(g->box_arena, UI_BoxBin, UI_NumBoxLookupBins);
for (u64 i = 0; i < countof(g->frames); ++i)
UI.box_arena = AcquireArena(Gibi(64));
/* Init frames */
for (u64 i = 0; i < countof(UI.frames); ++i)
{
UI_Frame *frame = &g->frames[i];
UI_Frame *frame = &UI.frames[i];
frame->arena = AcquireArena(Gibi(64));
frame->rects_arena = AcquireArena(Gibi(64));
frame->gpu_arena = G_AcquireArena();
}
/* Init root box */
{
UI_Box *box = PushStruct(UI.box_arena, UI_Box);
box->key = UI_RootKey;
UI.boxes_count += 1;
UI_BoxBin *bin = &UI.box_bins[box->key.hash % countof(UI.box_bins)];
bin->first = box;
bin->last = box;
UI.root_box = box;
}
}
//////////////////////////////
//- Begin frame
u64 last_frame_idx = g->current_frame_idx;
u64 frame_idx = last_frame_idx + 1;
if (frame_idx >= countof(g->frames))
{
frame_idx = 0;
}
g->current_frame_idx = frame_idx;
UI_Frame *last_frame = &g->frames[last_frame_idx];
UI_Frame *frame = &g->frames[frame_idx];
UI.current_frame_tick += 1;
UI_Frame *last_frame = UI_LastFrame();
UI_Frame *frame = UI_CurrentFrame();
{
Arena *old_arena = frame->arena;
@ -500,7 +600,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
i64 dt_ns = now_ns - last_frame->time_ns;
frame->time_ns = now_ns;
frame->dt_ns = dt_ns;
frame->tick = last_frame->tick + 1;
frame->tick = UI.current_frame_tick;
}
/* Init style stack */
@ -538,7 +638,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
}
/* Init box reports */
for (u64 pre_index = g->boxes_count; pre_index-- > 0;)
for (u64 pre_index = UI.boxes_count; pre_index-- > 0;)
{
UI_Box *box = last_frame->boxes_pre[pre_index];
if (hovered_box == 0 && box->desc.flags & UI_BoxFlag_Interactable)
@ -627,7 +727,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
}
/* Update box hot & active states */
for (u64 pre_index = 0; pre_index < g->boxes_count; ++pre_index)
for (u64 pre_index = 0; pre_index < UI.boxes_count; ++pre_index)
{
UI_Box *box = last_frame->boxes_pre[pre_index];
UI_Report *report = &box->report;
@ -666,8 +766,12 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
UI_Frame *UI_CurrentFrame(void)
{
UI_State *g = &UI_state;
return &g->frames[g->current_frame_idx];
return &UI.frames[UI.current_frame_tick % countof(UI.frames)];
};
UI_Frame *UI_LastFrame(void)
{
return &UI.frames[(UI.current_frame_tick - 1) % countof(UI.frames)];
};
Arena *UI_FrameArena(void)
@ -686,7 +790,7 @@ Vec2 UI_CursorPos(void)
void UI_EndFrame(UI_Frame *frame)
{
TempArena scratch = BeginScratchNoConflict();
UI_State *g = &UI_state;
UI_BoxIter box_iter = Zi;
Vec2I32 monitor_size = frame->window_frame.monitor_size;
Rng3 monitor_viewport = RNG3(VEC3(0, 0, 0), VEC3(monitor_size.x, monitor_size.y, 1));
@ -699,7 +803,51 @@ void UI_EndFrame(UI_Frame *frame)
//////////////////////////////
//- Process commands
g->boxes_count = 0;
{
//////////////////////////////
//- Create boxes from build cmds
for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{
UI_Cmd cmd = cmd_node->cmd;
if (cmd.kind == UI_CmdKind_BuildBox)
{
UI_Key key = cmd.box.key;
UI_Box *box = 0;
{
UI_BoxBin *bin = &UI.box_bins[key.hash % countof(UI.box_bins)];
for (box = bin->first; box; box = box->next_in_bin)
{
if (UI_MatchKey(box->key, key))
{
break;
}
}
/* Allocate new box */
if (box == 0)
{
/* Allocate new box */
box = UI.first_free_box;
if (box)
{
SllStackPop(UI.first_free_box);
ZeroStruct(box);
}
else
{
box = PushStruct(UI.box_arena, UI_Box);
}
box->key = key;
DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
++UI.boxes_count;
}
}
box->last_build_tick = frame->tick;
}
}
//////////////////////////////
//- Update boxes from cmds
for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{
@ -710,93 +858,45 @@ void UI_EndFrame(UI_Frame *frame)
case UI_CmdKind_BuildBox:
{
UI_Key key = cmd.box.key;
if (key.hash == 0)
if (UI_MatchKey(key, UI_NilKey))
{
key = UI_TransKey();
}
UI_Box *box = UI_BoxFromKey(key);
b32 is_root = frame->root_box == 0;
UI_Box *parent = 0;
if (!is_root)
if (box != UI.root_box)
{
parent = UI_BoxFromKey(cmd.box.parent);
if (!parent)
{
parent = frame->root_box;
parent = UI.root_box;
}
}
/* Allocate box */
UI_Box *box = 0;
/* Update parent */
if (box->parent != parent)
{
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
for (UI_Box *tmp = bin->first; tmp && !box; tmp = tmp->next_in_bin)
{
if (tmp->key.hash == key.hash)
{
box = tmp;
}
}
if (box)
{
/* Remove box from old parent */
if (box->parent)
{
/* Remove from old parent */
DllQueueRemove(box->parent->first, box->parent->last, box);
--box->parent->count;
}
}
else
if (parent)
{
box = g->first_free_box;
if (box)
{
SllStackPop(g->first_free_box);
ZeroStruct(box);
/* Add to new parent */
DllQueuePush(parent->first, parent->last, box);
++parent->count;
}
else
{
box = PushStruct(g->box_arena, UI_Box);
box->parent = parent;
}
DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
}
}
++g->boxes_count;
/* Reset box */
UI_Box old_box = *box;
{
ZeroStruct(box);
box->next_in_bin = old_box.next_in_bin;
box->prev_in_bin = old_box.prev_in_bin;
box->report = old_box.report;
}
box->key = key;
box->last_updated_tick = frame->tick;
/* Update box */
{
box->desc = cmd.box;
/* Insert box into parent */
if (parent)
{
DllQueuePush(parent->first, parent->last, box);
box->parent = parent;
++parent->count;
}
/* Fetch run */
if (box->desc.text.len > 0)
{
box->glyph_run = GC_RunFromString(frame->arena, box->desc.text, box->desc.font, box->desc.font_size);
}
}
if (is_root)
{
frame->root_box = box;
}
} break;
case UI_CmdKind_SetRawTexture:
@ -810,73 +910,90 @@ void UI_EndFrame(UI_Frame *frame)
}
} break;
}
}
}
//////////////////////////////
//- Prune boxes
//- Prune cached boxes
// {
// u64 cur_tick = frame->tick;
// UI_BoxIter it = UI_ITER(g->root_box);
// UI_Box *box = UI_NextBox(&it);
// while (box != 0)
// {
// UI_Box *next = UI_NextBox(&it);
// box = next;
// }
// }
{
u64 prunes_count = 0;
UI_Box **prunes = PushStructsNoZero(scratch.arena, UI_Box *, UI.boxes_count);
for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter))
{
if (ir.pre)
{
UI_Box *box = ir.box;
if (box->last_build_tick < frame->tick)
{
prunes[prunes_count++] = box;
}
}
}
for (u64 prune_idx = 0; prune_idx < prunes_count; ++prune_idx)
{
UI_Box *box = prunes[prune_idx];
UI_Box *parent = box->parent;
UI_BoxBin *bin = &UI.box_bins[box->key.hash % countof(UI.box_bins)];
/* Re-parent children */
if (box->first)
{
for (UI_Box *child = box->first; child; child = child->next)
{
child->parent = parent;
}
if (parent->last)
{
parent->last->next = box->first;
box->first->prev = parent->last;
}
else
{
parent->first = box->first;
}
parent->last = box->last;
parent->count += box->count;
}
/* Remove from parent */
DllQueueRemove(parent->first, parent->last, box);
--parent->count;
/* Remove from lookup table */
DllQueueRemoveNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
/* Add to free list */
SllStackPush(UI.first_free_box, box);
--UI.boxes_count;
}
}
//////////////////////////////
//- Layout
/* Build pre-order & post-order box arrays */
u64 boxes_count = g->boxes_count;
/* Prepare layout data */
u64 boxes_count = UI.boxes_count;
UI_Box **boxes_pre = PushStructsNoZero(frame->arena, UI_Box *, boxes_count);
UI_Box **boxes_post = PushStructsNoZero(frame->arena, UI_Box *, boxes_count);
frame->boxes_pre = boxes_pre;
frame->boxes_post = boxes_post;
{
Struct(BoxNode) { BoxNode *next; b32 visited; UI_Box *box; };
BoxNode *first_dfs = PushStruct(scratch.arena, BoxNode);
u64 pre_index = 0;
u64 post_index = 0;
first_dfs->box = frame->root_box;
while (first_dfs)
for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter))
{
BoxNode *n = first_dfs;
UI_Box *box = n->box;
if (!n->visited)
UI_Box *box = ir.box;
if (ir.pre)
{
/* Push floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
child_n->box = child;
SllStackPush(first_dfs, child_n);
}
}
/* Push non-floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
child_n->box = child;
SllStackPush(first_dfs, child_n);
}
}
box->pre_index = pre_index++;
boxes_pre[box->pre_index] = box;
n->visited = 1;
box->pre_index = pre_index;
boxes_pre[pre_index] = box;
pre_index += 1;
/* Reset layout data */
ZeroStruct(&box->layout);
}
else
{
SllStackPop(first_dfs);
box->post_index = post_index++;
boxes_post[box->post_index] = box;
box->post_index = post_index;
boxes_post[post_index] = box;
post_index += 1;
}
}
Assert(pre_index == boxes_count);
@ -892,7 +1009,7 @@ void UI_EndFrame(UI_Frame *frame)
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Pixel)
{
box->solved_dims[axis] = pref_size.v;
box->layout.solved_dims[axis] = pref_size.v;
}
else if (pref_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
{
@ -906,7 +1023,7 @@ void UI_EndFrame(UI_Frame *frame)
{
text_size = box->glyph_run.font_ascent + box->glyph_run.font_descent;
}
box->solved_dims[axis] = text_size + (pref_size.v * 2);
box->layout.solved_dims[axis] = text_size + (pref_size.v * 2);
}
}
}
@ -929,11 +1046,11 @@ void UI_EndFrame(UI_Frame *frame)
if (ancestor_size.kind == UI_SizeKind_Pixel || (ancestor_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText)))
{
/* Match independent ancestor */
match_size = ancestor->solved_dims[axis];
match_size = ancestor->layout.solved_dims[axis];
found_match = 1;
}
}
box->solved_dims[axis] = match_size * pref_size.v;
box->layout.solved_dims[axis] = match_size * pref_size.v;
}
}
}
@ -954,15 +1071,15 @@ void UI_EndFrame(UI_Frame *frame)
{
if (axis == box->desc.child_layout_axis)
{
accum += child->solved_dims[axis];
accum += child->layout.solved_dims[axis];
}
else
{
accum = MaxF32(child->solved_dims[axis], accum);
accum = MaxF32(child->layout.solved_dims[axis], accum);
}
}
}
box->solved_dims[axis] = accum + (pref_size.v * 2);
box->layout.solved_dims[axis] = accum + (pref_size.v * 2);
}
}
}
@ -977,7 +1094,7 @@ void UI_EndFrame(UI_Frame *frame)
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Grow)
{
box->solved_dims[axis] = box->parent->solved_dims[axis] * pref_size.v;
box->layout.solved_dims[axis] = box->parent->layout.solved_dims[axis] * pref_size.v;
}
}
}
@ -988,7 +1105,7 @@ void UI_EndFrame(UI_Frame *frame)
UI_Box *box = boxes_pre[pre_index];
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
{
f32 box_size = box->solved_dims[axis];
f32 box_size = box->layout.solved_dims[axis];
/* Solve non-floating violations */
{
f32 size_accum = 0;
@ -997,7 +1114,7 @@ void UI_EndFrame(UI_Frame *frame)
{
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
f32 size = child->solved_dims[axis];
f32 size = child->layout.solved_dims[axis];
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
if (axis == box->desc.child_layout_axis)
@ -1020,7 +1137,7 @@ void UI_EndFrame(UI_Frame *frame)
{
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
f32 size = child->solved_dims[axis];
f32 size = child->layout.solved_dims[axis];
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
f32 new_size = size;
@ -1037,24 +1154,24 @@ void UI_EndFrame(UI_Frame *frame)
}
}
adjusted_size_accum += new_size;
child->solved_dims[axis] = new_size;
child->layout.solved_dims[axis] = new_size;
}
}
size_accum = adjusted_size_accum;
}
box->final_children_size_accum[axis] = size_accum;
box->layout.final_children_size_accum[axis] = size_accum;
}
/* Solve floating violations */
for (UI_Box *child = box->first; child; child = child->next)
{
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating) && !AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClamp))
{
f32 size = child->solved_dims[axis];
f32 size = child->layout.solved_dims[axis];
if (size > box_size)
{
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
child->solved_dims[axis] = MaxF32(size - flex, box_size);
child->layout.solved_dims[axis] = MaxF32(size - flex, box_size);
}
}
}
@ -1071,25 +1188,25 @@ void UI_EndFrame(UI_Frame *frame)
{
Axis axis = box->desc.child_layout_axis;
UI_AxisAlignment alignment = box->desc.child_alignment[axis];
f32 box_size = box->solved_dims[axis];
f32 size_accum = box->final_children_size_accum[axis];
f32 box_size = box->layout.solved_dims[axis];
f32 size_accum = box->layout.final_children_size_accum[axis];
switch(alignment)
{
default: break;
case UI_AxisAlignment_Center:
{
box->layout_cursor = box_size / 2 - size_accum / 2;
box->layout.cursor = box_size / 2 - size_accum / 2;
} break;
case UI_AxisAlignment_End:
{
box->layout_cursor = box_size - size_accum;
box->layout.cursor = box_size - size_accum;
} break;
}
}
/* Position */
{
f32 *dims_arr = box->solved_dims;
f32 *dims_arr = box->layout.solved_dims;
Vec2 dims_vec = VEC2(dims_arr[0], dims_arr[1]);
Vec2 final_pos = Zi;
@ -1113,7 +1230,7 @@ void UI_EndFrame(UI_Frame *frame)
/* Non-floating box position */
else if (parent)
{
f32 layout_cursor = parent->layout_cursor;
f32 layout_cursor = parent->layout.cursor;
f32 offset[2] = Zi;
/* Compute offset in layout direction */
{
@ -1129,13 +1246,13 @@ void UI_EndFrame(UI_Frame *frame)
default: break;
case UI_AxisAlignment_Center:
{
f32 parent_size = parent->solved_dims[axis];
f32 parent_size = parent->layout.solved_dims[axis];
f32 box_size = dims_arr[axis];
offset[axis] = parent_size / 2 - box_size / 2;
} break;
case UI_AxisAlignment_End:
{
f32 parent_size = parent->solved_dims[axis];
f32 parent_size = parent->layout.solved_dims[axis];
f32 box_size = dims_arr[axis];
offset[axis] = parent_size - box_size;
} break;
@ -1143,7 +1260,7 @@ void UI_EndFrame(UI_Frame *frame)
}
final_pos.x = parent->rect.p0.x + offset[0];
final_pos.y = parent->rect.p0.y + offset[1];
parent->layout_cursor += dims_arr[parent->desc.child_layout_axis];
parent->layout.cursor += dims_arr[parent->desc.child_layout_axis];
}
/* Submit position */

View File

@ -2,7 +2,7 @@
//~ Key types
#define UI_NilKey ((UI_Key) { 0 })
#define UI_RootKey ((UI_Key) { 0xa3deb3749ef35a7aUll })
#define UI_RootKey ((UI_Key) { 0xaaaaaaaaaaaaaaaaUll })
Struct(UI_Key)
{
@ -253,7 +253,7 @@ Struct(UI_Box)
UI_Box *next_in_bin;
UI_Box *prev_in_bin;
UI_Report report;
u64 last_updated_tick;
i64 last_build_tick;
//- Tree links
UI_Box *parent;
@ -267,16 +267,19 @@ Struct(UI_Box)
UI_BoxDesc desc;
G_Texture2DRef raw_texture;
Rng2 raw_texture_slice_uv;
GC_Run glyph_run;
//- Pre-layout data
u64 pre_index;
u64 post_index;
//- Layout data
GC_Run glyph_run;
f32 layout_cursor;
struct
{
f32 cursor;
f32 solved_dims[Axis_CountXY];
f32 final_children_size_accum[Axis_CountXY];
} layout;
//- Layout results
Rng2 rect;
@ -293,9 +296,30 @@ Struct(UI_BoxBin)
};
////////////////////////////////////////////////////////////
//~ State types
//~ Iterator types
#define UI_NumBoxLookupBins 16384
Struct(UI_BoxIterDfsNode)
{
UI_BoxIterDfsNode *next;
UI_Box *box;
b32 visited;
};
Struct(UI_BoxIter)
{
UI_BoxIterDfsNode *first;
UI_BoxIterDfsNode *first_free;
UI_BoxIterDfsNode *last_free;
};
Struct(UI_BoxIterResult)
{
UI_Box *box;
b32 pre;
};
////////////////////////////////////////////////////////////
//~ Context types
Enum(UI_FrameFlag)
{
@ -314,10 +338,8 @@ Struct(UI_Frame)
G_ResourceHandle backbuffer;
G_CommandListHandle cl;
u64 transient_key_seed;
/* Time */
u64 tick;
i64 tick;
i64 time_ns;
i64 dt_ns;
@ -338,21 +360,25 @@ Struct(UI_Frame)
UI_StyleNode *first_free_style_node;
/* Layout */
UI_Box *root_box;
UI_Box **boxes_pre;
UI_Box **boxes_post;
};
Struct(UI_State)
Struct(UI_Ctx)
{
Arena *box_arena;
UI_BoxBin *box_bins;
u64 boxes_count;
Arena *box_arena;
UI_Box *root_box;
UI_BoxBin box_bins[Kibi(256)];
UI_Box *first_free_box;
u64 current_frame_idx;
u64 transient_key_seed;
i64 current_frame_tick;
UI_Frame frames[2];
} extern UI_state;
};
extern UI_Ctx UI;
////////////////////////////////////////////////////////////
//~ Bootstrap
@ -367,14 +393,19 @@ GC_FontKey UI_GetDefaultFont(void);
////////////////////////////////////////////////////////////
//~ Key helpers
b32 UI_MatchKey(UI_Key a, UI_Key b);
UI_Key UI_KeyFromString(String str);
UI_Key UI_KeyF_(String fmt, ...);
#define UI_KeyF(fmt_cstr, ...) UI_KeyF_(StringFromCstrNoLimit(fmt_cstr), __VA_ARGS__, FmtEnd)
UI_Key UI_TransKey(void);
UI_Box *UI_BoxFromKey(UI_Key key);
////////////////////////////////////////////////////////////
//~ Iteration helpers
UI_BoxIterResult UI_FirstBox(Arena *arena, UI_BoxIter *iter, UI_Key start_key);
UI_BoxIterResult UI_NextBox(Arena *arena, UI_BoxIter *iter);
////////////////////////////////////////////////////////////
//~ String helpers
@ -454,6 +485,7 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color);
//~ Frame helpers
UI_Frame *UI_CurrentFrame(void);
UI_Frame *UI_LastFrame(void);
Arena *UI_FrameArena(void);
Vec2 UI_CursorPos(void);