made a new hashtable container: HMapChained

Will be used isntead of the zpl in some places
This commit is contained in:
Edward R. Gonzalez 2024-05-14 11:47:44 -04:00
parent 12c99bee26
commit 26771ff2fd
23 changed files with 512 additions and 259 deletions

View File

@ -13,6 +13,7 @@ import "core:prof/spall"
import rl "vendor:raylib"
Path_Assets :: "../assets/"
Path_Shaders :: "../shaders/"
Path_Input_Replay :: "scratch.sectr_replay"
Persistent_Slab_DBG_Name := "Peristent Slab"
@ -276,12 +277,10 @@ reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem,
slab_reload( persistent_slab, persistent_allocator() )
font_provider_data.font_cache.hashes.backing = persistent_slab_allocator()
font_provider_data.font_cache.entries.backing = persistent_slab_allocator()
hmap_chained_reload( font_provider_data.font_cache, persistent_slab_allocator())
slab_reload( string_cache.slab, persistent_allocator() )
string_cache.table.hashes.backing = persistent_slab_allocator()
string_cache.table.entries.backing = persistent_slab_allocator()
zpl_hmap_reload( & string_cache.table, persistent_slab_allocator())
slab_reload( frame_slab, frame_allocator())
slab_reload( transient_slab, transient_allocator())

View File

@ -118,7 +118,7 @@ logger_interface :: proc(
}
log :: proc( msg : string, level := LogLevel.Info, loc := #caller_location ) {
core_log.logf( level, msg, location = loc )
core_log.log( level, msg, location = loc )
}
logf :: proc( fmt : string, args : ..any, level := LogLevel.Info, loc := #caller_location ) {

View File

@ -55,7 +55,8 @@ FontDef :: struct {
}
FontProviderData :: struct {
font_cache : HMapZPL(FontDef),
// font_cache : HMapZPL(FontDef),
font_cache : HMapChainedPtr(FontDef),
}
font_provider_startup :: proc()
@ -65,7 +66,7 @@ font_provider_startup :: proc()
font_provider_data := & get_state().font_provider_data; using font_provider_data
font_cache_alloc_error : AllocatorError
font_cache, font_cache_alloc_error = zpl_hmap_init_reserve( FontDef, persistent_slab_allocator(), 2 )
font_cache, font_cache_alloc_error = hmap_chained_init(FontDef, hmap_closest_prime(1 * Kilo), persistent_slab_allocator() )
verify( font_cache_alloc_error == AllocatorError.None, "Failed to allocate font_cache" )
log("font_cache created")
@ -76,10 +77,11 @@ font_provider_shutdown :: proc()
{
font_provider_data := & get_state().font_provider_data; using font_provider_data
for id in 0 ..< font_cache.entries.num
for & entry in font_cache.lookup
{
def := & font_cache.entries.data[id].value
if entry == nil do continue
def := entry.value
for & px_render in def.size_table {
using px_render
rl.UnloadFontData( glyphs, count )
@ -117,11 +119,10 @@ font_load :: proc( path_file : string,
}
key := cast(u64) crc32( transmute([]byte) desired_id )
def, set_error := zpl_hmap_set( & font_cache, key,FontDef {} )
def, set_error := hmap_chained_set(font_cache, key, FontDef{})
verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" )
def.path_file = path_file
// def.data = font_data
def.default_size = i32(points_to_pixels(default_size))
// TODO(Ed): this is slow & eats quite a bit of memory early on. Setup a more on demand load for a specific size.
@ -175,7 +176,7 @@ to_rl_Font :: proc( id : FontID, size := Font_Use_Default_Size ) -> rl.Font
even_size := math.round(size * (1.0/f32(Font_Size_Interval))) * f32(Font_Size_Interval)
size := clamp( i32( even_size), 4, Font_Largest_Px_Size )
def := zpl_hmap_get( & font_cache, id.key )
def := hmap_chained_get( font_cache, id.key )
size = size if size != i32(Font_Use_Default_Size) else def.default_size
id := (size / Font_Size_Interval) + (size % Font_Size_Interval)

View File

@ -38,11 +38,11 @@ array_underlying_slice :: proc(slice: []($ Type)) -> Array(Type)
return array_ptr ^
}
array_to_slice_num :: proc( using self : Array($ Type) ) -> []Type {
array_to_slice :: proc( using self : Array($ Type) ) -> []Type {
return slice_ptr( data, int(num) )
}
array_to_slice :: proc( using self : Array($ Type) ) -> []Type {
array_to_slice_capacity :: proc( using self : Array($ Type) ) -> []Type {
return slice_ptr( data, int(capacity))
}
@ -66,7 +66,7 @@ array_init_reserve :: proc
// log( str_fmt_tmp("array reserved: %d", header_size + int(capacity) * size_of(Type) ))
if alloc_error != AllocatorError.None do return
result.header = cast( ^ArrayHeader(Type)) raw_mem;
result.header = cast( ^ArrayHeader(Type)) raw_mem
result.backing = allocator
// result.dbg_name = dbg_name
result.fixed_cap = fixed_cap

View File

@ -3,7 +3,7 @@ package sectr
import "core:fmt"
import "core:os"
import "core:runtime"
import "base:runtime"
// Test

View File

@ -1,4 +0,0 @@
// TODO(Ed) : Roll own hashmap implementation (open-addressing, round-robin possibly)
package sectr

View File

@ -0,0 +1,224 @@
/*
Separate chaining hashtable with tombstone (vacancy aware)
This is an alternative to odin's map and the zpl hashtable I first used for this codebase.
I haven't felt the need to go back to dealing with odin's map for my edge case hot reload/memory replay failure.
So this is a hahstable loosely based at what I saw in the raddbg codebase.
It uses a fixed-size lookup table for the base layer of entries that can be chained.
Each slot keeps track of its vacancy (tombstone, is occupied).
If its occupied a new slot is chained using the fixed bucket-size pool allocator which will have its blocks sized to the type of the table.
This is ideal for tables have an indeterminate scope for how entires are added,
and direct pointers are kept across the codebase instead of a key to the slot.
*/
package sectr
import "core:mem"
HTable_Minimum_Capacity :: 4 * Kilobyte
HMapChainedSlot :: struct( $Type : typeid ) {
using links : DLL_NodePN(HMapChainedSlot(Type)),
value : Type,
key : u64,
occupied : b32,
}
HMapChained :: struct( $ Type : typeid ) {
pool : Pool,
lookup : [] ^HMapChainedSlot(Type),
}
HMapChainedPtr :: struct( $ Type : typeid) {
using header : ^HMapChained(Type),
}
// Provides the nearest prime number value for the given capacity
hmap_closest_prime :: proc( capacity : uint ) -> uint
{
prime_table : []uint = {
53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739,
6291469, 12582917, 25165843, 50331653, 100663319,
201326611, 402653189, 805306457, 1610612741, 3221225473, 6442450941
};
for slot in prime_table {
if slot >= capacity {
return slot
}
}
return prime_table[len(prime_table) - 1]
}
hmap_chained_init :: proc( $Type : typeid, lookup_capacity : uint, allocator : Allocator,
pool_bucket_cap : uint = 0,
pool_bucket_reserve_num : uint = 0,
pool_alignment : uint = mem.DEFAULT_ALIGNMENT,
dbg_name : string = ""
) -> (table : HMapChainedPtr(Type), error : AllocatorError)
{
header_size := size_of(HMapChainedPtr(Type))
size := header_size + int(lookup_capacity) * size_of( ^HMapChainedSlot(Type)) + size_of(int)
raw_mem : rawptr
raw_mem, error = alloc( size, allocator = allocator )
if error != AllocatorError.None do return
table.header = cast( ^HMapChained(Type)) raw_mem
table.pool, error = pool_init(
should_zero_buckets = false,
block_size = size_of(HMapChainedSlot(Type)),
bucket_capacity = pool_bucket_cap,
bucket_reserve_num = pool_bucket_reserve_num,
alignment = pool_alignment,
allocator = allocator,
dbg_name = str_intern(str_fmt_tmp("%: pool", dbg_name)).str
)
data := transmute([^] ^HMapChainedSlot(Type)) (transmute( [^]HMapChained(Type)) table.header)[1:]
table.lookup = slice_ptr( data, int(lookup_capacity) )
return
}
hmap_chained_clear :: proc( using self : HMapChainedPtr($Type))
{
for slot in lookup
{
if slot == nil {
continue
}
for probe_slot = slot.next; probe_slot != nil; probe_slot = probe_slot.next {
slot.occupied = false
}
slot.occupied = false
}
}
hmap_chained_destroy :: proc( using self : ^HMapChainedPtr($Type)) {
pool_destroy( pool )
free( self.header, backing)
self = nil
}
hmap_chained_lookup_id :: #force_inline proc( using self : HMapChainedPtr($Type), key : u64 ) -> u64
{
hash_index := key % u64( len(lookup) )
return hash_index
}
hmap_chained_get :: proc( using self : HMapChainedPtr($Type), key : u64) -> ^Type
{
// profile(#procedure)
surface_slot := lookup[hmap_chained_lookup_id(self, key)]
if surface_slot == nil {
return nil
}
if surface_slot.occupied && surface_slot.key == key {
return & surface_slot.value
}
for slot := surface_slot.next; slot != nil; slot = slot.next {
if slot.occupied && slot.key == key {
return & surface_slot.value
}
}
return nil
}
hmap_chained_reload :: proc( self : HMapChainedPtr($Type), allocator : Allocator )
{
pool_reload(self.pool, allocator)
}
// Returns true if an slot was actually found and marked as vacant
// Entries already found to be vacant will not return true
hmap_chained_remove :: proc( self : HMapChainedPtr($Type), key : u64 ) -> b32
{
surface_slot := lookup[hmap_chained_lookup_id(self, key)]
if surface_slot == nil {
return false
}
if surface_slot.occupied && surface_slot.key == key {
surface_slot.occupied = false
return true
}
for slot := surface_slot.next; slot != nil; slot.next
{
if slot.occupied && slot.key == key {
slot.occupied = false
return true
}
}
return false
}
// Sets the value to a vacant slot
// Will preemptively allocate the next slot in the hashtable if its null for the slot.
hmap_chained_set :: proc( using self : HMapChainedPtr($Type), key : u64, value : Type ) -> (^ Type, AllocatorError)
{
hash_index := hmap_chained_lookup_id(self, key)
surface_slot := lookup[hash_index]
set_slot :: #force_inline proc( using self : HMapChainedPtr(Type),
slot : ^HMapChainedSlot(Type),
key : u64,
value : Type
) -> (^ Type, AllocatorError )
{
error := AllocatorError.None
if slot.next == nil {
block : []byte
block, error = pool_grab(pool)
next := transmute( ^HMapChainedSlot(Type)) & block[0]
slot.next = next
next.prev = slot
}
slot.key = key
slot.value = value
slot.occupied = true
return & slot.value, error
}
if surface_slot == nil {
block, error := pool_grab(pool)
surface_slot := transmute( ^HMapChainedSlot(Type)) & block[0]
surface_slot.key = key
surface_slot.value = value
surface_slot.occupied = true
if error != AllocatorError.None {
ensure(error != AllocatorError.None, "Allocation failure for chained slot in hash table")
return nil, error
}
lookup[hash_index] = surface_slot
block, error = pool_grab(pool)
next := transmute( ^HMapChainedSlot(Type)) & block[0]
surface_slot.next = next
next.prev = surface_slot
return & surface_slot.value, error
}
if ! surface_slot.occupied
{
result, error := set_slot( self, surface_slot, key, value)
return result, error
}
slot := surface_slot.next
for ; slot != nil; slot = slot.next
{
if !slot.occupied
{
result, error := set_slot( self, surface_slot, key, value)
return result, error
}
}
ensure(false, "Somehow got to a null slot that wasn't preemptively allocated from a previus set")
return nil, AllocatorError.None
}

View File

@ -4,12 +4,11 @@ The only reason I may need this is due to issues with allocator callbacks or som
with hot-reloads...
This implementation uses two ZPL-Based Arrays to hold entires and the actual hash table.
Its algorithim isn't that great, removal of elements is very expensive.
Growing the hashtable doesn't do a resize on the original arrays properly, leading to completely discarded memory.
Its recommended to use something closer to raddbg's implementation for greater flexibility.
Instead of using separate chains, it maintains linked entries within the array.
Each entry contains a next field, which is an index pointing to the next entry in the same array.
This should only be used if you want the hashtable to also store the values
and an open-addressing hashtable is for some reason not desired.
Growing this hashtable is destructive, so it should usually be kept to a fixed-size unless
the populating operations only occur in one place and from then on its read-only.
*/
package sectr
@ -36,8 +35,8 @@ HMapZPL_Entry :: struct ( $ Type : typeid) {
}
HMapZPL :: struct ( $ Type : typeid ) {
hashes : Array( i64 ),
entries : Array( HMapZPL_Entry(Type) ),
table : Array( i64 ),
entries : Array( HMapZPL_Entry(Type) ),
}
zpl_hmap_init :: proc( $ Type : typeid, allocator : Allocator ) -> ( HMapZPL( Type), AllocatorError ) {
@ -48,15 +47,15 @@ zpl_hmap_init_reserve :: proc
( $ Type : typeid, allocator : Allocator, num : u64, dbg_name : string = "" ) -> ( HMapZPL( Type), AllocatorError )
{
result : HMapZPL(Type)
hashes_result, entries_result : AllocatorError
table_result, entries_result : AllocatorError
result.hashes, hashes_result = array_init_reserve( i64, allocator, num, dbg_name = dbg_name )
if hashes_result != AllocatorError.None {
ensure( false, "Failed to allocate hashes array" )
return result, hashes_result
result.table, table_result = array_init_reserve( i64, allocator, num, dbg_name = dbg_name )
if table_result != AllocatorError.None {
ensure( false, "Failed to allocate table array" )
return result, table_result
}
array_resize( & result.hashes, num )
slice.fill( slice_ptr( result.hashes.data, cast(int) result.hashes.num), -1 )
array_resize( & result.table, num )
slice.fill( slice_ptr( result.table.data, cast(int) result.table.num), -1 )
result.entries, entries_result = array_init_reserve( HMapZPL_Entry(Type), allocator, num, dbg_name = dbg_name )
if entries_result != AllocatorError.None {
@ -67,17 +66,17 @@ zpl_hmap_init_reserve :: proc
}
zpl_hmap_clear :: proc( using self : ^ HMapZPL( $ Type ) ) {
for id := 0; id < hashes.num; id += 1 {
hashes[id] = -1
for id := 0; id < table.num; id += 1 {
table[id] = -1
}
array_clear( hashes )
array_clear( table )
array_clear( entries )
}
zpl_hmap_destroy :: proc( using self : ^ HMapZPL( $ Type ) ) {
if hashes.data != nil && hashes.capacity > 0 {
array_free( hashes )
if table.data != nil && table.capacity > 0 {
array_free( table )
array_free( entries )
}
}
@ -119,7 +118,7 @@ zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorE
ensure( false, "ZPL HMAP IS REHASHING" )
last_added_index : i64
new_ht, init_result := zpl_hmap_init_reserve( Type, ht.hashes.backing, new_num, ht.hashes.dbg_name )
new_ht, init_result := zpl_hmap_init_reserve( Type, ht.table.backing, new_num, ht.table.dbg_name )
if init_result != AllocatorError.None {
ensure( false, "New zpl_hmap failed to allocate" )
return init_result
@ -133,7 +132,7 @@ zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorE
last_added_index = zpl_hmap_add_entry( & new_ht, entry.key )
if find_result.prev_index < 0 {
new_ht.hashes.data[ find_result.hash_index ] = last_added_index
new_ht.table.data[ find_result.hash_index ] = last_added_index
}
else {
new_ht.entries.data[ find_result.prev_index ].next = last_added_index
@ -154,15 +153,15 @@ zpl_hmap_rehash_fast :: proc( using self : ^ HMapZPL( $ Type ) )
for id := 0; id < entries.num; id += 1 {
entries[id].Next = -1;
}
for id := 0; id < hashes.num; id += 1 {
hashes[id] = -1
for id := 0; id < table.num; id += 1 {
table[id] = -1
}
for id := 0; id < entries.num; id += 1 {
entry := & entries[id]
find_result := zpl_hmap_find( entry.key )
if find_result.prev_index < 0 {
hashes[ find_result.hash_index ] = id
table[ find_result.hash_index ] = id
}
else {
entries[ find_result.prev_index ].next = id
@ -170,6 +169,12 @@ zpl_hmap_rehash_fast :: proc( using self : ^ HMapZPL( $ Type ) )
}
}
// Used when the address space of the allocator changes and the backing reference must be updated
zpl_hmap_reload :: proc( using self : ^HMapZPL($Type), new_backing : Allocator ) {
table.backing = new_backing
entries.backing = new_backing
}
zpl_hmap_remove :: proc( self : ^ HMapZPL( $ Type ), key : u64 ) {
find_result := zpl_hmap_find( key )
@ -208,7 +213,7 @@ zpl_hmap_set :: proc( using self : ^ HMapZPL( $ Type), key : u64, value : Type )
entries.data[ find_result.prev_index ].next = id
}
else {
hashes.data[ find_result.hash_index ] = id
table.data[ find_result.hash_index ] = id
}
}
@ -223,8 +228,8 @@ zpl_hmap_set :: proc( using self : ^ HMapZPL( $ Type), key : u64, value : Type )
}
zpl_hmap_slot :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> i64 {
for id : i64 = 0; id < hashes.num; id += 1 {
if hashes.data[id] == key {
for id : i64 = 0; id < table.num; id += 1 {
if table.data[id] == key {
return id
}
}
@ -243,9 +248,9 @@ zpl_hmap_find :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> HMapZPL_F
// profile(#procedure)
result : HMapZPL_FindResult = { -1, -1, -1 }
if hashes.num > 0 {
result.hash_index = cast(i64)( key % hashes.num )
result.entry_index = hashes.data[ result.hash_index ]
if table.num > 0 {
result.hash_index = cast(i64)( key % table.num )
result.entry_index = table.data[ result.hash_index ]
verify( result.entry_index < i64(entries.num), "Entry index is larger than the number of entries" )
@ -263,7 +268,7 @@ zpl_hmap_find :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> HMapZPL_F
}
zpl_hmap_full :: proc( using self : ^ HMapZPL( $ Type) ) -> b32 {
critical_load := u64(HMapZPL_CritialLoadScale * cast(f64) hashes.num)
critical_load := u64(HMapZPL_CritialLoadScale * cast(f64) table.num)
result : b32 = entries.num > critical_load
return result
}

View File

@ -4,7 +4,7 @@ package sectr
import "core:fmt"
import "core:mem"
import "core:mem/virtual"
import "core:runtime"
import "base:runtime"
import "core:os"
kilobytes :: #force_inline proc "contextless" ( kb : $ integer_type ) -> integer_type {

View File

@ -113,7 +113,7 @@ memtracker_unregister :: proc( tracker : MemoryTracker, to_remove : MemoryTracke
temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:])
context.temp_allocator = arena_allocator(& temp_arena)
entries := array_to_slice_num(tracker.entries)
entries := array_to_slice(tracker.entries)
for idx in 0..< tracker.entries.num
{
entry := & entries[idx]
@ -142,7 +142,7 @@ memtracker_check_for_collisions :: proc ( tracker : MemoryTracker )
temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:])
context.temp_allocator = arena_allocator(& temp_arena)
entries := array_to_slice_num(tracker.entries)
entries := array_to_slice(tracker.entries)
for idx in 1 ..< tracker.entries.num {
// Check to make sure each allocations adjacent entries do not intersect
left := & entries[idx - 1]

View File

@ -32,7 +32,7 @@ string_to_runes_array :: proc( content : string, allocator := context.allocator
return nil, alloc_error
}
runes := array_to_slice(runes_array)
runes := array_to_slice_capacity(runes_array)
idx := 0
for codepoint in content {

View File

@ -4,6 +4,15 @@ package sectr
import "core:math"
// These are the same as the runtime constants for memory units just using a more general name when not refering to bytes
Kilo :: Kilobyte
Mega :: Megabyte
Giga :: Gigabyte
Tera :: Terabyte
Peta :: Petabyte
Exa :: Exabyte
Axis2 :: enum i32 {
Invalid = -1,
X = 0,
@ -64,7 +73,7 @@ Vec3i :: [3]i32
vec2i_to_vec2 :: #force_inline proc "contextless" (v : Vec2i) -> Vec2 {return transmute(Vec2) v}
vec3i_to_vec3 :: #force_inline proc "contextless" (v : Vec3i) -> Vec3 {return transmute(Vec3) v}
//region Range2
#region("Range2")
Range2 :: struct #raw_union {
using min_max : struct {
@ -120,4 +129,4 @@ size_range2 :: #force_inline proc "contextless" ( value : Range2 ) -> Vec2 {
return { value.p1.x - value.p0.x, value.p0.y - value.p1.y }
}
//endregion Range2
#endregion("Range2")

View File

@ -89,9 +89,9 @@ PWS_ParseError :: struct {
}
PWS_ParseError_Max :: 32
PWS_TokenArray_ReserveSize :: 64 * Kilobyte
PWS_NodeArray_ReserveSize :: 64 * Kilobyte
PWS_LineArray_ReserveSize :: 64 * Kilobyte
PWS_TokenArray_ReserveSize :: 128
PWS_NodeArray_ReserveSize :: 32 * Kilobyte
PWS_LineArray_ReserveSize :: 32
// TODO(Ed) : The ast arrays should be handled by a slab allocator dedicated to PWS_ASTs
// This can grow in undeterministic ways, persistent will get very polluted otherwise.
@ -258,6 +258,7 @@ pws_parser_parse :: proc( text : string, allocator : Allocator ) -> ( PWS_ParseR
log( str_fmt_tmp( "parsing: %v ...", (len(text) > 30 ? transmute(string) bytes[ :30] : text) ))
// TODO(Ed): Change this to use a node pool
nodes, alloc_error = array_init_reserve( PWS_AST, allocator, PWS_NodeArray_ReserveSize )
verify( alloc_error == nil, "Allocation failure creating nodes array")

View File

@ -4,7 +4,7 @@ import "core:encoding/json"
import "core:fmt"
import "core:os"
import "core:reflect"
import "core:runtime"
import "base:runtime"
import "core:strings"
@(private="file")

View File

@ -116,7 +116,7 @@ render_mode_2d_workspace :: proc()
state.ui_context = ui
current := root.first
for & current in array_to_slice_num(ui.render_queue)
for & current in array_to_slice(ui.render_queue)
{
profile("Box")
style := current.style
@ -332,7 +332,7 @@ render_screen_ui :: proc()
break Render_App_UI
}
for & current in array_to_slice_num(ui.render_queue)
for & current in array_to_slice(ui.render_queue)
{
// profile("Box")
style := current.style

188
code/ui_box.odin Normal file
View File

@ -0,0 +1,188 @@
package sectr
UI_BoxFlag :: enum u64 {
Disabled,
Focusable,
Click_To_Focus,
Mouse_Clickable,
Keyboard_Clickable,
// Pan_X,
// Pan_Y,
// Scroll_X,
// Scroll_Y,
// Screenspace,
Count,
}
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
// UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y }
UI_RenderBoxInfo :: struct {
using computed : UI_Computed,
using style : UI_Style,
text : StrRunesPair,
font_size : UI_Scalar,
border_width : UI_Scalar,
}
UI_Box :: struct {
// Cache ID
key : UI_Key,
// label : string,
label : StrRunesPair,
text : StrRunesPair,
// Regenerated per frame.
// first, last : The first and last child of this box
// prev, next : The adjacent neighboring boxes who are children of to the same parent
using links : DLL_NodeFull( UI_Box ),
parent : ^UI_Box,
num_children : i32,
ancestors : i32, // This value for rooted widgets gets set to -1 after rendering see ui_box_make() for the reason.
parent_index : i32,
flags : UI_BoxFlags,
computed : UI_Computed,
layout : UI_Layout,
style : UI_Style,
// Persistent Data
hot_delta : f32,
active_delta : f32,
disabled_delta : f32,
style_delta : f32,
first_frame : b8,
// root_order_id : i16,
// prev_computed : UI_Computed,
// prev_style : UI_Style,v
// mouse : UI_InteractState,
// keyboard : UI_InteractState,
}
ui_box_equal :: #force_inline proc "contextless" ( a, b : ^ UI_Box ) -> b32 {
BoxSize :: size_of(UI_Box)
result : b32 = true
result &= a.key == b.key // We assume for now the label is the same as the key, if not something is terribly wrong.
result &= a.flags == b.flags
return result
}
ui_box_from_key :: #force_inline proc ( cache : ^HMapZPL(UI_Box), key : UI_Key ) -> (^UI_Box) {
return zpl_hmap_get( cache, cast(u64) key )
}
ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
{
// profile(#procedure)
using ui := get_state().ui_context
key := ui_key_from_string( label )
links_perserved : DLL_NodeFull( UI_Box )
curr_box : (^ UI_Box)
prev_box := zpl_hmap_get( prev_cache, cast(u64) key )
{
// profile("Assigning current box")
set_result : ^ UI_Box
set_error : AllocatorError
if prev_box != nil
{
// Previous history was found, copy over previous state.
set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, (prev_box ^) )
}
else {
box : UI_Box
box.key = key
box.label = str_intern( label )
// set_result, set_error = zpl_hmap_set( prev_cache, cast(u64) key, box )
set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, box )
}
verify( set_error == AllocatorError.None, "Failed to set zpl_hmap due to allocator error" )
curr_box = set_result
curr_box.first_frame = prev_box == nil
}
curr_box.flags = flags
// Clear non-persistent data
curr_box.computed.fresh = false
curr_box.links = links_perserved
curr_box.num_children = 0
// If there is a parent, setup the relevant references
parent := stack_peek( & parent_stack )
if parent != nil
{
dll_full_push_back( parent, curr_box, nil )
when false
{
// |
// v
// parent.first <nil>
if parent.first == nil {
parent.first = curr_box
parent.last = curr_box
curr_box.next = nil
curr_box.prev = nil
}
else {
// Positin is set to last, insert at end
// <parent.last.prev> <parent.last> curr_box
parent.last.next = curr_box
curr_box.prev = parent.last
parent.last = curr_box
curr_box.next = nil
}
}
curr_box.parent_index = parent.num_children
parent.num_children += 1
curr_box.parent = parent
curr_box.ancestors = parent.ancestors + 1
}
ui.built_box_count += 1
return curr_box
}
ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box)
{
parent := box.parent
// Check to make sure parent is present on the screen, if its not don't bother.
// If current has children, do them first
using state := get_state()
if box.first != nil
{
is_app_ui := ui_context == & screen_ui
if is_app_ui || intersects_range2( view_get_bounds(), box.computed.bounds)
{
return box.first
}
}
if box.next == nil
{
// There is no more adjacent nodes
if box.parent != nil
{
// Lift back up to parent, and set it to its next.
return parent.next
}
}
return box.next
}

View File

@ -39,6 +39,14 @@ ui_floating_startup :: proc( self : ^UI_FloatingManager, allocator : Allocator,
return error
}
ui_floating_reload :: proc( self : ^UI_FloatingManager, allocator : Allocator )
{
using self
build_queue.backing = allocator
tracked.entries.backing = allocator
tracked.table.backing = allocator
}
ui_floating_just_builder :: #force_inline proc( label : string, builder : UI_FloatingBuilder ) -> ^UI_Floating
{
No_Captures : rawptr = nil
@ -84,7 +92,7 @@ ui_floating_build :: proc()
screen_ui := cast(^UI_ScreenState) ui
using floating := get_state().ui_floating_context
for to_enqueue in array_to_slice_num( build_queue)
for to_enqueue in array_to_slice( build_queue)
{
key := ui_key_from_string(to_enqueue.label)
lookup := zpl_hmap_get( & tracked, transmute(u64) key )

View File

@ -193,7 +193,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
label_id := 0
line_id := 0
for line in array_to_slice_num( debug.lorem_parse.lines )
for line in array_to_slice( debug.lorem_parse.lines )
{
if line_id == 0 {
line_id += 1

View File

@ -52,27 +52,6 @@ UI_AnchorPresets :: enum u32 {
Count,
}
UI_BoxFlag :: enum u64 {
Disabled,
Focusable,
Click_To_Focus,
Mouse_Clickable,
Keyboard_Clickable,
// Pan_X,
// Pan_Y,
// Scroll_X,
// Scroll_Y,
// Screenspace,
Count,
}
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
// UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y }
UI_Cursor :: struct {
placeholder : int,
}
@ -91,13 +70,7 @@ UI_InteractState :: struct {
UI_Key :: distinct u64
UI_RenderBoxInfo :: struct {
using computed : UI_Computed,
using style : UI_Style,
text : StrRunesPair,
font_size : UI_Scalar,
border_width : UI_Scalar,
}
UI_Scalar :: f32
@ -107,42 +80,6 @@ UI_ScalarConstraint :: struct {
UI_Scalar2 :: [Axis2.Count]UI_Scalar
UI_Box :: struct {
// Cache ID
key : UI_Key,
// label : string,
label : StrRunesPair,
text : StrRunesPair,
// Regenerated per frame.
// first, last : The first and last child of this box
// prev, next : The adjacent neighboring boxes who are children of to the same parent
using links : DLL_NodeFull( UI_Box ),
parent : ^UI_Box,
num_children : i32,
ancestors : i32, // This value for rooted widgets gets set to -1 after rendering see ui_box_make() for the reason.
parent_index : i32,
flags : UI_BoxFlags,
computed : UI_Computed,
layout : UI_Layout,
style : UI_Style,
// Persistent Data
hot_delta : f32,
active_delta : f32,
disabled_delta : f32,
style_delta : f32,
first_frame : b8,
// root_order_id : i16,
// prev_computed : UI_Computed,
// prev_style : UI_Style,v
// mouse : UI_InteractState,
// keyboard : UI_InteractState,
}
// UI_BoxFlags_Stack_Size :: 512
UI_Layout_Stack_Size :: 512
@ -220,137 +157,16 @@ ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_rese
ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator )
{
// We need to repopulate Allocator references
for cache in & ui.caches {
cache.entries.backing = cache_allocator
cache.hashes.backing = cache_allocator
for & cache in & ui.caches {
zpl_hmap_reload( & cache, cache_allocator)
}
ui.render_queue.backing = cache_allocator
}
// TODO(Ed) : Is this even needed?
ui_shutdown :: proc() {
}
ui_box_equal :: #force_inline proc "contextless" ( a, b : ^ UI_Box ) -> b32 {
BoxSize :: size_of(UI_Box)
result : b32 = true
result &= a.key == b.key // We assume for now the label is the same as the key, if not something is terribly wrong.
result &= a.flags == b.flags
return result
}
ui_box_from_key :: #force_inline proc ( cache : ^HMapZPL(UI_Box), key : UI_Key ) -> (^UI_Box) {
return zpl_hmap_get( cache, cast(u64) key )
}
ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
{
// profile(#procedure)
using ui := get_state().ui_context
key := ui_key_from_string( label )
links_perserved : DLL_NodeFull( UI_Box )
curr_box : (^ UI_Box)
prev_box := zpl_hmap_get( prev_cache, cast(u64) key )
{
// profile("Assigning current box")
set_result : ^ UI_Box
set_error : AllocatorError
if prev_box != nil
{
// Previous history was found, copy over previous state.
set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, (prev_box ^) )
}
else {
box : UI_Box
box.key = key
box.label = str_intern( label )
// set_result, set_error = zpl_hmap_set( prev_cache, cast(u64) key, box )
set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, box )
}
verify( set_error == AllocatorError.None, "Failed to set zpl_hmap due to allocator error" )
curr_box = set_result
curr_box.first_frame = prev_box == nil
}
curr_box.flags = flags
// Clear non-persistent data
curr_box.computed.fresh = false
curr_box.links = links_perserved
curr_box.num_children = 0
// If there is a parent, setup the relevant references
parent := stack_peek( & parent_stack )
if parent != nil
{
dll_full_push_back( parent, curr_box, nil )
when false
{
// |
// v
// parent.first <nil>
if parent.first == nil {
parent.first = curr_box
parent.last = curr_box
curr_box.next = nil
curr_box.prev = nil
}
else {
// Positin is set to last, insert at end
// <parent.last.prev> <parent.last> curr_box
parent.last.next = curr_box
curr_box.prev = parent.last
parent.last = curr_box
curr_box.next = nil
}
}
curr_box.parent_index = parent.num_children
parent.num_children += 1
curr_box.parent = parent
curr_box.ancestors = parent.ancestors + 1
}
ui.built_box_count += 1
return curr_box
}
ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box)
{
parent := box.parent
// Check to make sure parent is present on the screen, if its not don't bother.
// If current has children, do them first
using state := get_state()
if box.first != nil
{
is_app_ui := ui_context == & screen_ui
if is_app_ui || intersects_range2( view_get_bounds(), box.computed.bounds)
{
return box.first
}
}
if box.next == nil
{
// There is no more adjacent nodes
if box.parent != nil
{
// Lift back up to parent, and set it to its next.
return parent.next
}
}
return box.next
}
ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
if ui_context == & state.project.workspace.ui {

2
docs/ui.md Normal file
View File

@ -0,0 +1,2 @@
# UI

View File

@ -11,8 +11,12 @@
"enable_fake_methods": true,
"enable_format": false,
"enable_hover": true,
"enable_semantic_tokens": true,
"enable_semantic_tokens": false,
"enable_snippets": false,
"enable_references": true,
"thread_pool_count": 10
"thread_pool_count": 10,
"enable_inlay_hints": true,
"enable_procedure_context": true,
"enable_procedure_snippet": false,
"disable_parser_errors": false
}

View File

@ -169,9 +169,9 @@ push-location $path_root
# $build_args += $flag_micro_architecture_native
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
# $build_args += $flag_optimize_none
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
$build_args += $flag_optimize_speed
# $build_args += $flag_optimize_speed
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
@ -251,10 +251,10 @@ push-location $path_root
# $build_args += $flag_micro_architecture_native
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
# $build_args += $flag_optimize_none
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
$build_args += $falg_optimize_aggressive
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'