made a new hashtable container: HMapChained
Will be used isntead of the zpl in some places
This commit is contained in:
		@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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  ) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package sectr
 | 
			
		||||
 | 
			
		||||
import "core:fmt"
 | 
			
		||||
import "core:os"
 | 
			
		||||
import "core:runtime"
 | 
			
		||||
import "base:runtime"
 | 
			
		||||
 | 
			
		||||
// Test
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
// TODO(Ed) : Roll own hashmap implementation (open-addressing, round-robin possibly)
 | 
			
		||||
package sectr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										224
									
								
								code/grime_hashmap_chained.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								code/grime_hashmap_chained.odin
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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]
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										188
									
								
								code/ui_box.odin
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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 )
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										192
									
								
								code/ui_ui.odin
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								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 <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
									
								
							
							
						
						
									
										2
									
								
								docs/ui.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# UI
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								ols.json
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user