From 9b959ef86920d82c93fb3438c5498ca3809209dc Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 14 Feb 2024 02:29:28 -0500 Subject: [PATCH] Initial impl pass for grime array and hashtable --- code/grime.odin | 4 +- code/grime_array.odin | 239 +++++++++++++++++++++++++++++++++++ code/grime_hashtable.odin | 258 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 code/grime_array.odin create mode 100644 code/grime_hashtable.odin diff --git a/code/grime.odin b/code/grime.odin index a3c9d6c..2d0c917 100644 --- a/code/grime.odin +++ b/code/grime.odin @@ -2,6 +2,7 @@ package sectr // At least its less than C/C++ ... +import "base:builtin" import "core:mem" import "core:mem/virtual" import "core:path/filepath" @@ -27,6 +28,7 @@ terabyte :: proc ( tb : $ integer_type ) -> integer_type { return tb * Terabyte } +copy :: builtin.copy Allocator :: mem.Allocator AllocatorError :: mem.Allocator_Error alloc :: mem.alloc @@ -34,6 +36,7 @@ alloc_bytes :: mem.alloc_bytes Arena :: mem.Arena arena_allocator :: mem.arena_allocator arena_init :: mem.arena_init +free :: mem.free ptr_offset :: mem.ptr_offset slice_ptr :: mem.slice_ptr Tracking_Allocator :: mem.Tracking_Allocator @@ -42,7 +45,6 @@ tracking_allocator_init :: mem.tracking_allocator_init file_name_from_path :: filepath.short_stem OS_Type :: type_of(ODIN_OS) - get_bounds :: proc { box_get_bounds, view_get_bounds, diff --git a/code/grime_array.odin b/code/grime_array.odin new file mode 100644 index 0000000..c9027ee --- /dev/null +++ b/code/grime_array.odin @@ -0,0 +1,239 @@ +// Based on gencpp's and thus zpl's Array implementation +// Made becasue of the map issue with fonts during hot-reload. +// I didn't want to make the HashTable impl with the [dynamic] array for now to isolate +// what in the world is going on with the memory... +package sectr + +import "core:c/libc" +import "core:mem" + +Array :: struct ( $ Type : typeid ) { + allocator : Allocator, + capacity : u64, + num : u64, + data : [^]Type, +} + +array_to_slice :: proc ( arr : Array( $ Type) ) -> []Type { + using arr; return slice_ptr( data, num ) +} + +array_grow_formula :: proc( value : u64 ) -> u64 { + return 2 * value + 8 +} + +array_init :: proc( $ Type : typeid, allocator : Allocator ) -> ( Array(Type), AllocatorError ) { + return array_init_reserve( Type, allocator, array_grow_formula(0) ) +} + +array_init_reserve :: proc( $ Type : typeid, allocator : Allocator, capacity : u64 ) -> ( Array(Type), AllocatorError ) +{ + raw_data, result_code = alloc( capacity * size_of(Type), allocator = allocator ) + result : Array( Type) + result.data = cast( [^] Type ) raw_data + result.allocator = allocator + result.capacity = capacity + return result, result_code +} + +array_append :: proc( array : ^ Array( $ Type), value : Type ) -> AllocatorError +{ + using array + if num == capacity + { + grow_result := array_grow( array, capacity ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + data[ num ] = value + num += 1 + return AllocatorError.None +} + +array_append_slice :: proc( array : ^ Array( $ Type ), items : []Type ) -> AllocatorError +{ + using array + if num + len(items) > capacity + { + grow_result := array_grow( array, capacity ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + // Note(Ed) : Original code from gencpp + // libc.memcpy( ptr_offset(data, num), raw_data(items), len(items) * size_of(Type) ) + + // TODO(Ed) : VERIFY VIA DEBUG THIS COPY IS FINE. + target := ptr_offset( data, num ) + copy( slice_ptr(target, capacity - num), items ) + + num += len(items) + return AllocatorError.None +} + +array_append_at :: proc( array : ^ Array( $ Type ), item : Type, id : u64 ) -> AllocatorError +{ + id := id + using array + + if id >= num { + id = num - 1 + } + if id < 0 { + id = 0 + } + + if capacity < num + 1 + { + grow_result := array_grow( array, capacity ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + target := & data[id] + + // TODO(Ed) : VERIFY VIA DEBUG THIS COPY IS FINE. + dst = slice_ptr( ptr_offset(target) + 1, num - id - 1 ) + src = slice_ptr( target, num - id ) + copy( dst, src ) + + // Note(Ed) : Original code from gencpp + // libc.memmove( ptr_offset(target, 1), target, (num - idx) * size_of(Type) ) + data[id] = item + num += 1 + return AllocatorError.None +} + +array_append_at_slice :: proc ( array : ^ Array( $ Type ), items : []Type, id : u64 ) -> AllocatorError +{ + id := id + using array + + if id >= num { + return array_append_slice( items ) + } + if len(items) > capacity + { + grow_result := array_grow( array, capacity ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + // Note(Ed) : Original code from gencpp + // target := ptr_offset( data, id + len(items) ) + // src := ptr_offset( data, id ) + // libc.memmove( target, src, num - id * size_of(Type) ) + // libc.memcpy ( src, raw_data(items), len(items) * size_of(Type) ) + + // TODO(Ed) : VERIFY VIA DEBUG THIS COPY IS FINE + target := & data[id + len(items)] + dst := slice_ptr( target, num - id - len(items) ) + src := slice_ptr( & data[id], num - id ) + copy( dst, src ) + copy( src, items ) + + num += len(items) + return AllocatorError.None +} + +array_back :: proc( array : ^ Array( $ Type ) ) -> ^ Type { + using array; return & data[ num - 1 ] +} + +array_clear :: proc ( array : ^ Array( $ Type ) ) { + array.num = 0 +} + +array_fill :: proc ( array : ^ Array( $ Type ), begin, end : u64, value : Type ) -> b32 +{ + using array + + if begin < 0 || end >= num { + return false + } + + for id := begin; id < end; id += 1 { + data[ id ] = value + } + return true +} + +array_free :: proc( array : ^ Array( $ Type ) ) { + using array + free( data, allocator ) + data = nil +} + +array_grow :: proc( array : ^ Array( $ Type ), min_capacity : u64 ) -> AllocatorError +{ + using array + new_capacity = grow_formula( capacity ) + + if new_capacity < min_capacity { + new_capacity = min_capacity + } + + return array_set_capacity( array, new_capacity ) +} + +array_pop :: proc( array : ^ Array( $ Type ) ) { + verify( array.num == 0, "Attempted to pop an array with no elements" ) + array.num -= 1 +} + +array_remove_at :: proc( array : ^ Array( $ Type ), id : u64 ) +{ + using array + verify( id >= num, "Attempted to remove from an index larger than the array" ) + + left = slice_ptr( data, id ) + right = slice_ptr( ptr_offset( memory_after(left), 1), num - len(left) - 1 ) + copy( left, right ) + + num -= 1 +} + +array_reserve :: proc( array : ^ Array( $ Type ), new_capacity : u64 ) -> AllocatorError +{ + using array + if capacity < new_capacity { + return array_set_capacity( array, new_capacity ) + } + return AllocatorError.None +} + +array_resize :: proc ( array : ^ Array( $ Type ), num : u64 ) -> AllocatorError +{ + if array.capacity < num + { + grow_result := array_grow( array, capacity ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + array.num = num + return AllocatorError.None +} + +array_set_capacity :: proc( array : ^ Array( $ Type ), new_capacity : u64 ) -> AllocatorError +{ + using array + if new_capacity == capacity { + return true + } + if new_capacity < num { + num = new_capacity + return true + } + + raw_data, result_code = alloc( 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 new file mode 100644 index 0000000..04b6d99 --- /dev/null +++ b/code/grime_hashtable.odin @@ -0,0 +1,258 @@ +// This is an alternative to Odin's default map type. +// The only reason I may need this is due to issues with allocator callbacks or something else going on +// with hot-reloads... +package sectr + +// This might be problematic... +HT_MapProc :: #type proc( $ Type : typeid, key : u64, value : Type ) +HT_MapMutProc :: #type proc( $ Type : typeid, key : u64, value : ^ Type ) + +HT_FindResult :: struct { + hash_index : i64, + prev_index : i64, + entry_index : i64, +} + +HashTable_Entry :: struct ( $ Type : typeid) { + key : u64, + next : u64, + value : Type, +} + +HashTable :: struct ( $ Type : typeid) { + hashes : Array( i64 ), + entries : Array( HashTable_Entry(Type) ), +} + +hashtable_init :: proc( $ Type : typeid, allocator : Allocator ) -> ( HashTable( Type), AllocatorError ) { + return hashtable_init_reserve( Type, allocator ) +} + +hashtable_init_reserve :: proc ( $ Type : typeid, allcoator : Allocator, num : u64 ) -> ( HashTable( Type), AllocatorError ) +{ + result : HashTable(Type) + hashes_result, entries_result : AllocatorError + + result.hashes, hashes_result = array_init_reserve( i64, allocator, num ) + if hashes_result != AllocatorError.None { + ensure( false, "Failed to allocate hashes array" ) + return result, hashes_result + } + + result.entries, entries_result = array_init_reserve( allocator, num ) + if entries_result != AllocatorError.None { + ensure( false, "Failed to allocate entries array" ) + return result, entries_result + } + return result, AllocatorError.None +} + +hashtable_clear :: proc( ht : ^ HashTable( $ Type ) ) { + using ht + for id := 0; id < hashes.num; id += 1 { + hashes[id] = -1 + } + + array_clear( hashes ) + array_clear( entries ) +} + +hashtable_destroy :: proc( ht : ^ HashTable( $ Type ) ) { + if hashes.data && hashes.capacity { + array_free( hashes ) + array_free( entries ) + } +} + +hashtable_get :: proc( ht : ^ HashTable( $ Type ), key : u64 ) -> ^ Type +{ + using ht + + id := hashtable_find( key ).entry_index + if id >= 0 { + return & entries[id].value + } + + return nil +} + +hashtable_map :: proc( ht : ^ HashTable( $ Type), map_proc : HT_MapProc ) { + using ht + ensure( map_proc != nil, "Mapping procedure must not be null" ) + for id := 0; id < entries.num; id += 1 { + map_proc( Type, entries[id].key, entries[id].value ) + } +} + +hashtable_map_mut :: proc( ht : ^ HashTable( $ Type), map_proc : HT_MapMutProc ) { + using ht + ensure( map_proc != nil, "Mapping procedure must not be null" ) + for id := 0; id < entries.num; id += 1 { + map_proc( Type, entries[id].key, & entries[id].value ) + } +} + +hashtable_grow :: proc( ht : ^ HashTable( $ Type ) ) -> AllocatorError { + new_num := array_grow_formula( entries.num ) + return rehash( ht, new_num ) +} + +hashtable_rehash :: proc ( ht : ^ HashTabe( $ Type ), new_num : i64 ) -> AllocatorError +{ + last_added_index : i64 + + new_ht, init_result := hashtable_init_reserve( Type, ht.hashes.allocator, new_num ) + if init_result != AllocatorError.None { + ensure( false, "New hashtable failed to allocate" ) + return init_result + } + + for id := 0; id < new_ht.hashes.num; id += 1 { + new_ht.hashes[id] = -1 + } + + for id := 0; id < ht.entries.num; id += 1 { + find_result : HT_FindResult + + if new_ht.hashes.num == 0 { + hashtable_grow( new_ht ) + } + + entry = & entries[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 + } + else { + new_ht.hashes[ 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 + } + + hashtable_destroy( ht ) + + (ht ^) = new_ht + return AllocatorError.None +} + +hashtable_rehash_fast :: proc ( ht : ^ HashTable( $ Type ) ) +{ + using ht + 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 < entries.num; id += 1 { + entry := & entries[id] + find_result := hashtable_find( entry.key ) + + if find_result.prev_index < 0 { + hashes[ find_result.hash_index ] = id + } + else { + entries[ find_result.prev_index ].next = id + } + } +} + +hashtable_remove :: proc ( ht : ^ HashTable( $ Type ), key : u64 ) { + using ht + find_result := hashtable_find( key ) + + if find_result.entry_index >= 0 { + array_remove_at( & ht.entries, find_result.entry_index ) + hashtable_rehash_fast( ht ) + } +} + +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 +{ + using ht + + id := 0 + find_result : HT_FindResult + + if hashes.num == 0 + { + grow_result := hashtable_grow( ht ) + if grow_result != AllocatorError.None { + return grow_result + } + } + + find_result = hashtable_find( key ) + if find_result.entry_index >= 0 { + id = find_result.entry_index + } + else + { + id = hashtable_add_entry( ht, key ) + if find_result.prev_index >= 0 { + entries[ find_result.prev_index ].next = id + } + else { + hashes[ find_result.hash_index ] = id + } + } + + entries[id].value = value + + if hashtable_full( ht ) { + return hashtable_grow( ht ) + } + + return 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 { + return id + } + } + return -1 +} + +hashtable_add_entry :: proc( ht : ^ HashTable( $ Type), key : u64 ) -> i64 { + using ht + entry : HashTable_Entry = { key, -1 } + id := 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 } + + if hashes.num > 0 { + result.hash_index = key % hash.num + result.entry_index = hashes[ result.hash_index ] + + for ; result.entry_index >= 0; { + if entries[ result.entry_index ].key == key { + break + } + + result.prev_index = result.entry_index + result.entry_index = entries[ result.entry_index ].next + } + } + return result +} + +hashtable_full :: proc( ht : ^ HashTable( $ Type) ) -> b32 { + return 0.75 * hashes.num < entries.num +}