From 9b3bc6fd68ce8fe6778182784ac3ba3e05cb5347 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 22 Feb 2024 23:15:29 -0500 Subject: [PATCH] Got this thing to compile and seem operable at runtime There was quite a few errors with the hashtable (not suprised). I need to use it more to see if it fails to work properly. For now it should be fine enough for prototyping --- code/api.odin | 12 ++--- code/env.odin | 5 ++ code/grime.odin | 38 +++++++++++++- code/grime_array.odin | 17 ++++--- code/grime_hashtable.odin | 103 ++++++++++++++++++++------------------ code/tick_update.odin | 8 ++- code/ui.odin | 92 ++++++++++++++++++++++++++++++++-- 7 files changed, 208 insertions(+), 67 deletions(-) diff --git a/code/api.odin b/code/api.odin index 591aa49..f30346c 100644 --- a/code/api.odin +++ b/code/api.odin @@ -143,13 +143,8 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ // projection = rl.CameraProjection.ORTHOGRAPHIC, // } - frame_1.color = Color_BG_TextBox - box_set_size( & frame_1, { 100, 50 } * CM_Per_Point ) - - frame_2.color = Color_BG_TextBox_Green - box_set_size( & frame_2, { 60, 100 } * CM_Per_Point ) - - + // Setup workspace UI state + ui_startup( & workspace.ui, persistent_allocator() ) } } } @@ -208,6 +203,9 @@ reload :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ L // font_provider_data := & get_state().font_provider_data // font_provider_data.font_cache.allocator = arena_allocator( & font_provider_data.font_arena ) + // Have to reload allocators for all dynamic allocating data-structures. + ui_reload( & get_state().project.workspace.ui, persistent_allocator() ) + log("Module reloaded") } diff --git a/code/env.odin b/code/env.odin index cca0ea3..6fda9e3 100644 --- a/code/env.odin +++ b/code/env.odin @@ -115,6 +115,11 @@ State :: struct { font_squidgy_slimes : FontID, font_rec_mono_semicasual_reg : FontID, default_font : FontID, + + // There are two potential UI contextes for this prototype so far, + // the screen-space UI and the current workspace UI. + // This is used so that the ui api doesn't need to have the user pass the context every single time. + ui_context : UI_State, } get_state :: proc "contextless" () -> ^ State { diff --git a/code/grime.odin b/code/grime.odin index 8d7af28..ed7f503 100644 --- a/code/grime.odin +++ b/code/grime.odin @@ -4,12 +4,14 @@ package sectr import "base:builtin" import "base:runtime" -import c "core:c/libc" +import "core:hash" import "core:mem" import "core:mem/virtual" import "core:os" import "core:path/filepath" +import c "core:c/libc" + Byte :: 1 Kilobyte :: 1024 * Byte Megabyte :: 1024 * Kilobyte @@ -32,6 +34,7 @@ terabyte :: proc ( tb : $ integer_type ) -> integer_type { } copy :: builtin.copy +crc32 :: hash.crc32 Allocator :: mem.Allocator AllocatorError :: mem.Allocator_Error alloc :: mem.alloc @@ -53,6 +56,39 @@ get_bounds :: proc { view_get_bounds, } +//region Stack - Basic fixed-size stack container +Stack :: struct ( $ Type : typeid, $ Size : i32 ) { + idx : i32, + items : [ Size ] Type, +} + +stack_push :: proc( stack : ^ $ StackType / Stack( $ Type, $ Size ), value : Type ) { + using stack + verify( idx < len( items ), "Attempted to push on a full stack" ) + + items[ idx ] = value + idx += 1 +} + +stack_pop :: proc( stack : ^ $ StackType / Stack( $ Type, $ Size ) ) { + using stack + verify( idx > 0, "Attempted to pop an empty stack" ) + + idx -= 1 +} + +stack_peek :: proc ( stack : ^ Stack( $ Type, $ Size ) ) -> ^ Type { + using stack + return & items[idx] +} +//endregion Stack + + + + + + + // TODO(Ed) : This is extremely jank, Raylib requires a 'heap' allocator with the way it works. diff --git a/code/grime_array.odin b/code/grime_array.odin index c9027ee..b36af3b 100644 --- a/code/grime_array.odin +++ b/code/grime_array.odin @@ -6,6 +6,7 @@ package sectr import "core:c/libc" import "core:mem" +import "core:slice" Array :: struct ( $ Type : typeid ) { allocator : Allocator, @@ -28,7 +29,7 @@ array_init :: proc( $ Type : typeid, allocator : Allocator ) -> ( Array(Type), A array_init_reserve :: proc( $ Type : typeid, allocator : Allocator, capacity : u64 ) -> ( Array(Type), AllocatorError ) { - raw_data, result_code = alloc( capacity * size_of(Type), allocator = allocator ) + raw_data, result_code := alloc( int(capacity) * size_of(Type), allocator = allocator ) result : Array( Type) result.data = cast( [^] Type ) raw_data result.allocator = allocator @@ -157,6 +158,10 @@ array_fill :: proc ( array : ^ Array( $ Type ), begin, end : u64, value : Type ) return false } + // TODO(Ed) : Bench this? + // data_slice := slice_ptr( ptr_offset( data, begin ), end - begin ) + // slice.fill( data_slice, cast(int) value ) + for id := begin; id < end; id += 1 { data[ id ] = value } @@ -172,7 +177,7 @@ array_free :: proc( array : ^ Array( $ Type ) ) { array_grow :: proc( array : ^ Array( $ Type ), min_capacity : u64 ) -> AllocatorError { using array - new_capacity = grow_formula( capacity ) + new_capacity := array_grow_formula( capacity ) if new_capacity < min_capacity { new_capacity = min_capacity @@ -211,7 +216,7 @@ array_resize :: proc ( array : ^ Array( $ Type ), num : u64 ) -> AllocatorError { if array.capacity < num { - grow_result := array_grow( array, capacity ) + grow_result := array_grow( array, array.capacity ) if grow_result != AllocatorError.None { return grow_result } @@ -225,14 +230,14 @@ array_set_capacity :: proc( array : ^ Array( $ Type ), new_capacity : u64 ) -> A { using array if new_capacity == capacity { - return true + return AllocatorError.None } if new_capacity < num { num = new_capacity - return true + return AllocatorError.None } - raw_data, result_code = alloc( new_capacity * size_of(Type), allocator = allocator ) + raw_data, result_code := alloc( cast(int) new_capacity * size_of(Type), allocator = allocator ) data = cast( [^] Type ) raw_data capacity = new_capacity return result_code diff --git a/code/grime_hashtable.odin b/code/grime_hashtable.odin index 40b4b18..c3c32c0 100644 --- a/code/grime_hashtable.odin +++ b/code/grime_hashtable.odin @@ -3,6 +3,9 @@ // with hot-reloads... package sectr +import "core:slice" + + // Note(Ed) : See core:hash for hasing procs. // This might be problematic... @@ -17,11 +20,11 @@ HT_FindResult :: struct { HashTable_Entry :: struct ( $ Type : typeid) { key : u64, - next : u64, + next : i64, value : Type, } -HashTable :: struct ( $ Type : typeid) { +HashTable :: struct ( $ Type : typeid ) { hashes : Array( i64 ), entries : Array( HashTable_Entry(Type) ), } @@ -30,7 +33,7 @@ hashtable_init :: proc( $ Type : typeid, allocator : Allocator ) -> ( HashTable( return hashtable_init_reserve( Type, allocator ) } -hashtable_init_reserve :: proc ( $ Type : typeid, allcoator : Allocator, num : u64 ) -> ( HashTable( Type), AllocatorError ) +hashtable_init_reserve :: proc ( $ Type : typeid, allocator : Allocator, num : u64 ) -> ( HashTable( Type), AllocatorError ) { result : HashTable(Type) hashes_result, entries_result : AllocatorError @@ -40,8 +43,11 @@ hashtable_init_reserve :: proc ( $ Type : typeid, allcoator : Allocator, num : u ensure( false, "Failed to allocate hashes array" ) return result, hashes_result } + array_resize( & result.hashes, num ) + slice.fill( slice_ptr( result.hashes.data, cast(int) result.hashes.num), -1 ) + // array_fill( result.hashes, 0, num - 1, -1 ) - result.entries, entries_result = array_init_reserve( allocator, num ) + result.entries, entries_result = array_init_reserve( HashTable_Entry(Type), allocator, num ) if entries_result != AllocatorError.None { ensure( false, "Failed to allocate entries array" ) return result, entries_result @@ -59,10 +65,10 @@ hashtable_clear :: proc( ht : ^ HashTable( $ Type ) ) { array_clear( entries ) } -hashtable_destroy :: proc( ht : ^ HashTable( $ Type ) ) { - if hashes.data && hashes.capacity { - array_free( hashes ) - array_free( entries ) +hashtable_destroy :: proc( using ht : ^ HashTable( $ Type ) ) { + if hashes.data != nil && hashes.capacity > 0 { + array_free( & hashes ) + array_free( & entries ) } } @@ -70,9 +76,9 @@ hashtable_get :: proc( ht : ^ HashTable( $ Type ), key : u64 ) -> ^ Type { using ht - id := hashtable_find( key ).entry_index + id := hashtable_find( ht, key ).entry_index if id >= 0 { - return & entries[id].value + return & entries.data[id].value } return nil @@ -95,11 +101,12 @@ hashtable_map_mut :: proc( ht : ^ HashTable( $ Type), map_proc : HT_MapMutProc ) } hashtable_grow :: proc( ht : ^ HashTable( $ Type ) ) -> AllocatorError { + using ht new_num := array_grow_formula( entries.num ) - return rehash( ht, new_num ) + return hashtable_rehash( ht, new_num ) } -hashtable_rehash :: proc ( ht : ^ HashTable( $ Type ), new_num : i64 ) -> AllocatorError +hashtable_rehash :: proc ( ht : ^ HashTable( $ Type ), new_num : u64 ) -> AllocatorError { last_added_index : i64 @@ -109,30 +116,27 @@ hashtable_rehash :: proc ( ht : ^ HashTable( $ Type ), new_num : i64 ) -> Alloca return init_result } - for id := 0; id < new_ht.hashes.num; id += 1 { - new_ht.hashes[id] = -1 - } + // for id : u64 = 0; id < new_ht.hashes.num; id += 1 { + // new_ht.hashes.data[id] = -1 + // } + slice.fill( slice_ptr( new_ht.hashes.data, cast(int) new_ht.hashes.num ), -1 ) - for id := 0; id < ht.entries.num; id += 1 { + for id : u64 = 0; id < ht.entries.num; id += 1 { find_result : HT_FindResult - if new_ht.hashes.num == 0 { - hashtable_grow( new_ht ) - } - - entry = & entries[id] + entry := & ht.entries.data[id] find_result = hashtable_find( & new_ht, entry.key ) last_added_index = hashtable_add_entry( & new_ht, entry.key ) if find_result.prev_index < 0 { - new_ht.hashes[ find_result.hash_index ] = last_added_index + new_ht.hashes.data[ find_result.hash_index ] = last_added_index } else { - new_ht.hashes[ find_result.prev_index ].next = last_added_index + new_ht.entries.data[ find_result.prev_index ].next = last_added_index } - new_ht.entries[ last_added_index ].next = find_result.entry_index - new_ht.entries[ last_added_index ].value = entry.value + new_ht.entries.data[ last_added_index ].next = find_result.entry_index + new_ht.entries.data[ last_added_index ].value = entry.value } hashtable_destroy( ht ) @@ -177,22 +181,22 @@ hashtable_remove_entry :: proc( ht : ^ HashTable( $ Type ), id : i64 ) { array_remove_at( & ht.entries, id ) } -hashtable_set :: proc( ht : ^ HashTable( $ Type), key : u64, value : Type ) -> AllocatorError +hashtable_set :: proc( ht : ^ HashTable( $ Type), key : u64, value : Type ) -> (^ Type, AllocatorError) { using ht - id := 0 + id : i64 = 0 find_result : HT_FindResult - if hashes.num == 0 + if hashtable_full( ht ) { - grow_result := hashtable_grow( ht ) + grow_result := hashtable_grow(ht) if grow_result != AllocatorError.None { - return grow_result + return nil, grow_result } } - find_result = hashtable_find( key ) + find_result = hashtable_find( ht, key ) if find_result.entry_index >= 0 { id = find_result.entry_index } @@ -200,26 +204,26 @@ hashtable_set :: proc( ht : ^ HashTable( $ Type), key : u64, value : Type ) -> A { id = hashtable_add_entry( ht, key ) if find_result.prev_index >= 0 { - entries[ find_result.prev_index ].next = id + entries.data[ find_result.prev_index ].next = id } else { - hashes[ find_result.hash_index ] = id + hashes.data[ find_result.hash_index ] = id } } - entries[id].value = value + entries.data[id].value = value if hashtable_full( ht ) { - return hashtable_grow( ht ) + return & entries.data[id].value, hashtable_grow( ht ) } - return AllocatorError.None + return & entries.data[id].value, AllocatorError.None } hashtable_slot :: proc( ht : ^ HashTable( $ Type), key : u64 ) -> i64 { using ht - for id := 0; id < hashes.num; id += 1 { - if hashes[id] == key { + for id : i64 = 0; id < hashes.num; id += 1 { + if hashes.data[id] == key { return id } } @@ -228,33 +232,34 @@ hashtable_slot :: proc( ht : ^ HashTable( $ Type), key : u64 ) -> i64 { hashtable_add_entry :: proc( ht : ^ HashTable( $ Type), key : u64 ) -> i64 { using ht - entry : HashTable_Entry = { key, -1 } - id := entries.num - array_append( entries, entry ) + entry : HashTable_Entry(Type) = { key, -1, {} } + id := cast(i64) entries.num + array_append( & entries, entry ) return id } hashtable_find :: proc( ht : ^ HashTable( $ Type), key : u64 ) -> HT_FindResult { using ht - find_result : HT_FindResult = { -1, -1, -1 } + result : HT_FindResult = { -1, -1, -1 } if hashes.num > 0 { - result.hash_index = key % hash.num - result.entry_index = hashes[ result.hash_index ] + result.hash_index = cast(i64)( key % hashes.num ) + result.entry_index = hashes.data[ result.hash_index ] - for ; result.entry_index >= 0; { - if entries[ result.entry_index ].key == key { + for ; result.entry_index >= 0; { + if entries.data[ result.entry_index ].key == key { break } result.prev_index = result.entry_index - result.entry_index = entries[ result.entry_index ].next + result.entry_index = entries.data[ result.entry_index ].next } } return result } -hashtable_full :: proc( ht : ^ HashTable( $ Type) ) -> b32 { - return 0.75 * hashes.num < entries.num +hashtable_full :: proc( using ht : ^ HashTable( $ Type) ) -> b32 { + result : b32 = entries.num > u64(0.75 * cast(f64) hashes.num) + return result } diff --git a/code/tick_update.odin b/code/tick_update.odin index 33f2a10..bf17faa 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -176,8 +176,14 @@ update :: proc( delta_time : f64 ) -> b32 //region Imgui Tick { - // Layout + ui_context := & state.project.workspace.ui + // Build Graph (Determines if layout is dirty) + ui_graph_build_begin( ui_context ) + + + + // Regnerate compute if layout is dirty. } // endregion diff --git a/code/ui.odin b/code/ui.odin index 77fb069..9c3fb27 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -138,7 +138,11 @@ UI_Layout :: struct { corner_radii : [Corner.Count]f32, - size : UI_ScalarConstraint, + // TODO(Ed) I problably won't use this as I can determine + // the size of content manually when needed and make helper procs... + // size_to_content : b32, + + size : Vec2, } UI_Style :: struct { @@ -187,11 +191,17 @@ UI_Box :: struct { disabled_time : f32, } +Layout_Stack_Size :: 512 +Style_Stack_Size :: 512 + UI_State :: struct { box_cache : HashTable( UI_Box ), - box_tree_dirty : b32, - root : ^ UI_Box, + root : ^ UI_Box, + + layout_dirty : b32, + layout_stack : Stack( UI_Layout, Layout_Stack_Size ), + style_stack : Stack( UI_Style, Style_Stack_Size ), hot : UI_Key, active : UI_Key, @@ -202,7 +212,83 @@ UI_State :: struct { // drag_state data : string, } +ui_key_from_string :: proc ( value : string ) -> UI_Key { + key := cast(UI_Key) crc32( transmute([]byte) value ) + return key +} + +ui_box_equal :: proc ( a, b : ^ UI_Box ) -> b32 { + BoxSize :: size_of(UI_Box) + hash_a := crc32( transmute([]u8) slice_ptr( a, BoxSize ) ) + hash_b := crc32( transmute([]u8) slice_ptr( b, BoxSize ) ) + result : b32 = hash_a == hash_b + return result +} + +ui_startup :: proc ( ui : ^ UI_State, cache_allocator : Allocator ) { + ui := ui + ui^ = {} + + box_cache, allocation_error := hashtable_init_reserve( UI_Box, cache_allocator, Kilobyte * 8 ) + verify( allocation_error != AllocatorError.None, "Failed to allocate box cache" ) + ui.box_cache = box_cache +} + +ui_reload :: proc ( ui : ^ UI_State, cache_allocator : Allocator ) { + // We need to repopulate Allocator references + ui.box_cache.entries.allocator = cache_allocator + ui.box_cache.hashes.allocator = cache_allocator +} + +// TODO(Ed) : Is this even needed? +ui_shutdown :: proc () { +} + +ui_graph_build_begin :: proc ( ui : ^ UI_State ) +{ + ui_context := & get_state().ui_context + ui_context = ui + using ui_context + + box : UI_Box = {} + box.label = "root#001" + box.key = ui_key_from_string( box.label ) + box.layout = stack_peek( & layout_stack ) ^ + box.style = stack_peek( & style_stack ) ^ + + cached_box := hashtable_get( & box_cache, cast(u64) box.key ) + + if cached_box != nil { + layout_dirty &= ! ui_box_equal( & box, cached_box ) + } + else { + layout_dirty = true + } + + set_result, set_error: = hashtable_set( & box_cache, cast(u64) box.key, box ) + verify( set_error != AllocatorError.None, "Failed to set hashtable due to allocator error" ) + root = set_result +} + ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) { + + return nil } + +ui_layout_push :: proc ( preset : UI_Layout ) { + +} + +ui_layout_pop :: proc () { + +} + +ui_layout_push_size :: proc( size : Vec2 ) { + +} + +ui_style_push :: proc ( preset : UI_Style ) { + +}