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 { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user