From 26771ff2fd5e267f49d8314b51263b82b6a710ee Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 14 May 2024 11:47:44 -0400 Subject: [PATCH] made a new hashtable container: HMapChained Will be used isntead of the zpl in some places --- code/{ui_screen.odin => app_screen.odin} | 0 code/engine_api.odin | 7 +- code/engine_logger.odin | 2 +- code/font_provider.odin | 15 +- code/grime_array.odin | 6 +- code/grime_filesystem.odin | 2 +- code/grime_hashmap.odin | 4 - code/grime_hashmap_chained.odin | 224 +++++++++++++++++++++++ code/grime_hashmap_zpl.odin | 67 +++---- code/grime_memory.odin | 2 +- code/grime_memory_tracker.odin | 4 +- code/grime_unicode.odin | 2 +- code/math_math.odin | 13 +- code/parser_whitespace.odin | 7 +- code/project_serialize.odin | 2 +- code/tick_render.odin | 4 +- code/ui_box.odin | 188 +++++++++++++++++++ code/ui_floating.odin | 10 +- code/ui_tests.odin | 2 +- code/ui_ui.odin | 192 +------------------ docs/ui.md | 2 + ols.json | 8 +- scripts/build.ps1 | 8 +- 23 files changed, 512 insertions(+), 259 deletions(-) rename code/{ui_screen.odin => app_screen.odin} (100%) delete mode 100644 code/grime_hashmap.odin create mode 100644 code/grime_hashmap_chained.odin create mode 100644 code/ui_box.odin create mode 100644 docs/ui.md diff --git a/code/ui_screen.odin b/code/app_screen.odin similarity index 100% rename from code/ui_screen.odin rename to code/app_screen.odin diff --git a/code/engine_api.odin b/code/engine_api.odin index 65c8166..1701bf2 100644 --- a/code/engine_api.odin +++ b/code/engine_api.odin @@ -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()) diff --git a/code/engine_logger.odin b/code/engine_logger.odin index 0edec33..5605fbf 100644 --- a/code/engine_logger.odin +++ b/code/engine_logger.odin @@ -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 ) { diff --git a/code/font_provider.odin b/code/font_provider.odin index 9b3f26d..ce666d3 100644 --- a/code/font_provider.odin +++ b/code/font_provider.odin @@ -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) diff --git a/code/grime_array.odin b/code/grime_array.odin index 58eaeaf..fb63f14 100644 --- a/code/grime_array.odin +++ b/code/grime_array.odin @@ -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 diff --git a/code/grime_filesystem.odin b/code/grime_filesystem.odin index 3b73ae3..ad35fdc 100644 --- a/code/grime_filesystem.odin +++ b/code/grime_filesystem.odin @@ -3,7 +3,7 @@ package sectr import "core:fmt" import "core:os" -import "core:runtime" +import "base:runtime" // Test diff --git a/code/grime_hashmap.odin b/code/grime_hashmap.odin deleted file mode 100644 index ac509f2..0000000 --- a/code/grime_hashmap.odin +++ /dev/null @@ -1,4 +0,0 @@ -// TODO(Ed) : Roll own hashmap implementation (open-addressing, round-robin possibly) -package sectr - - diff --git a/code/grime_hashmap_chained.odin b/code/grime_hashmap_chained.odin new file mode 100644 index 0000000..4fe9da8 --- /dev/null +++ b/code/grime_hashmap_chained.odin @@ -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 +} diff --git a/code/grime_hashmap_zpl.odin b/code/grime_hashmap_zpl.odin index 60f9063..88af50f 100644 --- a/code/grime_hashmap_zpl.odin +++ b/code/grime_hashmap_zpl.odin @@ -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 } diff --git a/code/grime_memory.odin b/code/grime_memory.odin index d9eb45d..e40da5a 100644 --- a/code/grime_memory.odin +++ b/code/grime_memory.odin @@ -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 { diff --git a/code/grime_memory_tracker.odin b/code/grime_memory_tracker.odin index 29eaa23..0aaf0bf 100644 --- a/code/grime_memory_tracker.odin +++ b/code/grime_memory_tracker.odin @@ -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] diff --git a/code/grime_unicode.odin b/code/grime_unicode.odin index b3bc8b1..b933517 100644 --- a/code/grime_unicode.odin +++ b/code/grime_unicode.odin @@ -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 { diff --git a/code/math_math.odin b/code/math_math.odin index cd2c124..8bc92bf 100644 --- a/code/math_math.odin +++ b/code/math_math.odin @@ -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") diff --git a/code/parser_whitespace.odin b/code/parser_whitespace.odin index e486b76..3f6b50d 100644 --- a/code/parser_whitespace.odin +++ b/code/parser_whitespace.odin @@ -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") diff --git a/code/project_serialize.odin b/code/project_serialize.odin index 303f688..07ac711 100644 --- a/code/project_serialize.odin +++ b/code/project_serialize.odin @@ -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") diff --git a/code/tick_render.odin b/code/tick_render.odin index 945b398..5de8d66 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -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 diff --git a/code/ui_box.odin b/code/ui_box.odin new file mode 100644 index 0000000..aa0a3df --- /dev/null +++ b/code/ui_box.odin @@ -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 + 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 + // 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 +} diff --git a/code/ui_floating.odin b/code/ui_floating.odin index 4c094e7..9a88357 100644 --- a/code/ui_floating.odin +++ b/code/ui_floating.odin @@ -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 ) diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 1f80ea7..0385ddd 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -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 diff --git a/code/ui_ui.odin b/code/ui_ui.odin index 35a5fcc..c0efd53 100644 --- a/code/ui_ui.odin +++ b/code/ui_ui.odin @@ -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 - 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 - // 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 { diff --git a/docs/ui.md b/docs/ui.md new file mode 100644 index 0000000..5ca2bec --- /dev/null +++ b/docs/ui.md @@ -0,0 +1,2 @@ +# UI + diff --git a/ols.json b/ols.json index bbefa3f..8aa3433 100644 --- a/ols.json +++ b/ols.json @@ -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 } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 746f663..7954386 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -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'