From 1e18592ff5058750bd11175757af8ef4d9b19063 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 21 Oct 2025 22:07:55 -0400 Subject: [PATCH] thinking about key tables... --- Readme.md | 46 +--- code/grime/pool_virtual.odin | 0 code2/grime/hashing.odin | 15 +- ...y_table_1_layer_chained_chunked_cells.odin | 164 --------------- code2/grime/key_table_1_layer_linear.odin | 48 ----- .../key_table_chained_chunked_cells.odin | 196 ++++++++++++++++++ code2/grime/key_table_inear.odin | 37 ++++ .../{key_table.odin => key_table_wip.odin} | 3 +- code2/grime/memory.odin | 17 ++ code2/grime/memory_tracker.odin | 2 +- code2/grime/string_cache.odin | 16 +- 11 files changed, 279 insertions(+), 265 deletions(-) delete mode 100644 code/grime/pool_virtual.odin delete mode 100644 code2/grime/key_table_1_layer_chained_chunked_cells.odin delete mode 100644 code2/grime/key_table_1_layer_linear.odin create mode 100644 code2/grime/key_table_chained_chunked_cells.odin create mode 100644 code2/grime/key_table_inear.odin rename code2/grime/{key_table.odin => key_table_wip.odin} (96%) diff --git a/Readme.md b/Readme.md index 8771fa3..5b50355 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,10 @@ This prototype aims to flesh out ideas I've wanted to explore futher on code editing & related tooling. -The things to explore: +Current goal with the prototype is just making a good visualizer & note aggregation for codebases & libraries. +My note repos with affine links give an idea of what that would look like. + +The things to explore (future): * 2D canvas for laying out code visualized in various types of ASTs * WYSIWYG frontend ASTs @@ -28,55 +31,14 @@ The dependencies are: * [sokol-odin (Sectr Fork)](https://github.com/Ed94/sokol-odin) * [sokol-tools](https://github.com/floooh/sokol-tools) * Powershell (if you want to use my build scripts) -* backtrace (not used yet) -* freetype (not used yet) * Eventually some config parser (maybe I'll use metadesk, or [ini](https://github.com/laytan/odin-ini-parser)) The project is so far in a "codebase boostrapping" phase. Most the work being done right now is setting up high performance linear zoom rendering for text and UI. Text has recently hit sufficient peformance targets, and now inital UX has become the focus. -The project's is organized into 2 runtime modules sectr_host & sectr. -The host module loads the main module & its memory. Hot-reloading it's dll when it detects a change. - -Codebase organization: - -* App: General app config, state, and operations. -* Engine: client interface for host, tick, update, rendering. - * Has the following definitions: startup, shutdown, reload, tick, clean_frame (which host hooks up to when managing the client dll) - * Will handle async ops. -* Font Provider: Manages fonts. - * Bulk of implementation maintained as a separate library: [VEFontCache-Odin](https://github.com/Ed94/VEFontCache-Odin) -* Grime: Name speaks for itself, stuff not directly related to the target features to iterate upon for the prototype. - * Defining dependency aliases or procedure overload tables, rolling own allocator, data structures, etc. -* Input: All human input related features - * Base input features (polling & related) are platform abstracted from sokol_app - * Entirely user rebindable -* Math: The usual for 2D/3D. -* Parsers: - * AST generation, editing, and serialization. - * Parsers for different levels of "synatitic & semantic awareness", Formatting -> Domain Specific AST - * Figure out pragmatic transformations between ASTs. -* Project: Encpasulation of user config/context/state separate from persistent app's - * Manages the codebase (database & model view controller) - * Manages workspaces : View compositions of the codebase -* UI: Core graphic user interface framework, AST visualzation & editing, backend visualization - * PIMGUI (Persistent Immediate Mode User Interface) - * Auto-layout - * Supports heavy procedural generation of box widgets - * Viewports - * Docking/Tiling, Floating, Canvas - -Due to the nature of the prototype there are 'sub-groups' such as the codebase being its own ordeal as well as the workspace. -They'll be elaborated in their own documentation - ## Gallery -![img](docs/assets/sectr_host_2024-03-09_04-30-27.png) -![img](docs/assets/sectr_host_2024-05-04_12-29-39.png) -![img](docs/assets/Code_2024-05-04_12-55-53.png) ![img](docs/assets/sectr_host_2024-05-11_22-34-15.png) -![img](docs/assets/sectr_host_2024-05-15_03-32-36.png) -![img](docs/assets/Code_2024-05-21_23-15-16.gif) ## Notes diff --git a/code/grime/pool_virtual.odin b/code/grime/pool_virtual.odin deleted file mode 100644 index e69de29..0000000 diff --git a/code2/grime/hashing.odin b/code2/grime/hashing.odin index e2a9987..ee8b88f 100644 --- a/code2/grime/hashing.odin +++ b/code2/grime/hashing.odin @@ -1,9 +1,20 @@ package grime -hash32_djb8 :: #force_inline proc "contextless" ( hash : ^u32, bytes : []byte ) { +hash32_djb8 :: #force_inline proc "contextless" (hash: ^u32, bytes: []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u32(value) } -hash64_djb8 :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) { +hash64_djb8 :: #force_inline proc "contextless" (hash: ^u64, bytes: []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + u64(value) } + +// Ripped from core:hash, fnv32a +@(optimization_mode="favor_size") +hash32_fnv1a :: #force_inline proc "contextless" (hash: ^u32, data: []byte, seed := u32(0x811c9dc5)) { + hash^ = seed; for b in data { hash^ = (hash^ ~ u32(b)) * 0x01000193 } +} + +@(optimization_mode="favor_size") +hash64_fnv1a :: #force_inline proc "contextless" (hash: ^u64, data: []byte, seed := u64(0xcbf29ce484222325)) { + hash^ = seed; for b in data { hash^ = (hash^ ~ u64(b)) * 0x100000001b3 } +} diff --git a/code2/grime/key_table_1_layer_chained_chunked_cells.odin b/code2/grime/key_table_1_layer_chained_chunked_cells.odin deleted file mode 100644 index 7c17c71..0000000 --- a/code2/grime/key_table_1_layer_chained_chunked_cells.odin +++ /dev/null @@ -1,164 +0,0 @@ -package grime - -import "base:intrinsics" - -/* -Key Table 1-Layer Chained-Chunked-Cells -*/ - -KT1CX_Slot :: struct($type: typeid) { - value: type, - key: u64, - occupied: b32, -} -KT1CX_Cell :: struct($type: typeid, $depth: int) { - slots: [depth]KT1CX_Slot(type), - next: ^KT1CX_Cell(type, depth), -} -KT1CX :: struct($cell: typeid) { - table: []cell, -} -KT1CX_Byte_Slot :: struct { - key: u64, - occupied: b32, -} -KT1CX_Byte_Cell :: struct { - next: ^byte, -} -KT1CX_Byte :: struct { - table: []byte, -} -KT1CX_ByteMeta :: struct { - slot_size: int, - slot_key_offset: uintptr, - cell_next_offset: uintptr, - cell_depth: int, - cell_size: int, - type_width: int, - type: typeid, -} -KT1CX_InfoMeta :: struct { - table_size: int, - slot_size: int, - slot_key_offset: uintptr, - cell_next_offset: uintptr, - cell_depth: int, - cell_size: int, - type_width: int, - type: typeid, -} -KT1CX_Info :: struct { - backing_table: AllocatorInfo, -} -kt1cx_init :: proc(info: KT1CX_Info, m: KT1CX_InfoMeta, result: ^KT1CX_Byte) { - assert(result != nil) - assert(info.backing_table.procedure != nil) - assert(m.cell_depth > 0) - assert(m.table_size >= 4 * Kilo) - assert(m.type_width > 0) - table_raw, error := mem_alloc(m.table_size * m.cell_size, ainfo = allocator(info.backing_table)) - assert(error == .None); slice_assert(transmute([]byte) table_raw) - (transmute(^SliceByte) & table_raw).len = m.table_size - result.table = table_raw -} -kt1cx_clear :: proc(kt: KT1CX_Byte, m: KT1CX_ByteMeta) { - cell_cursor := cursor(kt.table) - table_len := len(kt.table) * m.cell_size - for ; cell_cursor != end(kt.table); cell_cursor = cell_cursor[m.cell_size:] // for cell, cell_id in kt.table.cells - { - slots := SliceByte { cell_cursor, m.cell_depth * m.slot_size } // slots = cell.slots - slot_cursor := slots.data - for;; { - slot := slice(slot_cursor, m.slot_size) // slot = slots[slot_id] - zero(slot) // slot = {} - if slot_cursor == end(slots) { // if slot == end(slot) - next := slot_cursor[m.cell_next_offset:] // next = kt.table.cells[cell_id + 1] - if next != nil { // if next != nil - slots.data = next // slots = next.slots - slot_cursor = next - continue - } - } - slot_cursor = slot_cursor[m.slot_size:] // slot = slots[slot_id + 1] - } - } -} -kt1cx_slot_id :: proc(kt: KT1CX_Byte, key: u64, m: KT1CX_ByteMeta) -> u64 { - cell_size := m.cell_size // dummy value - hash_index := key % u64(len(kt.table)) - return hash_index -} -kt1cx_get :: proc(kt: KT1CX_Byte, key: u64, m: KT1CX_ByteMeta) -> ^byte { - hash_index := kt1cx_slot_id(kt, key, m) - cell_offset := uintptr(hash_index) * uintptr(m.cell_size) - cell_cursor := cursor(kt.table)[cell_offset:] // cell_id = 0 - { - slots := slice(cell_cursor, m.cell_depth * m.slot_size) // slots = cell[cell_id].slots - slot_cursor := cell_cursor // slot_id = 0 - for;; - { - slot := transmute(^KT1CX_Byte_Slot) slot_cursor[m.slot_key_offset:] // slot = cell[slot_id] - if slot.occupied && slot.key == key { - return cast(^byte) slot_cursor - } - if slot_cursor == end(slots) - { - cell_next := cell_cursor[m.cell_next_offset:] // cell.next - if cell_next != nil { - slots = slice(cell_next, len(slots)) // slots = cell.next - slot_cursor = cell_next - cell_cursor = cell_next // cell = cell.next - continue - } - else { - return nil - } - } - slot_cursor = slot_cursor[m.slot_size:] - } - } -} -kt1cx_set :: proc(kt: KT1CX_Byte, key: u64, value: []byte, backing_cells: Odin_Allocator, m: KT1CX_ByteMeta) -> ^byte { - hash_index := kt1cx_slot_id(kt, key, m) - cell_offset := uintptr(hash_index) * uintptr(m.cell_size) - cell_cursor := cursor(kt.table)[cell_offset:] // KT1CX_Cell(Type) cell = kt.table[hash_index] - { - slots := SliceByte {cell_cursor, m.cell_depth * m.slot_size} // cell.slots - slot_cursor := slots.data - for ;; - { - slot := transmute(^KT1CX_Byte_Slot) slot_cursor[m.slot_key_offset:] - if slot.occupied == false { - slot.occupied = true - slot.key = key - return cast(^byte) slot_cursor - } - else if slot.key == key { - return cast(^byte) slot_cursor - } - if slot_cursor == end(slots) { - curr_cell := transmute(^KT1CX_Byte_Cell) (uintptr(cell_cursor) + m.cell_next_offset) // curr_cell = cell - if curr_cell != nil { - slots.data = curr_cell.next - slot_cursor = curr_cell.next - cell_cursor = curr_cell.next - continue - } - else { - new_cell, _ := mem_alloc(m.cell_size, ainfo = backing_cells) - curr_cell.next = raw_data(new_cell) - slot = transmute(^KT1CX_Byte_Slot) cursor(new_cell)[m.slot_key_offset:] - slot.occupied = true - slot.key = key - return raw_data(new_cell) - } - } - slot_cursor = slot_cursor[m.slot_size:] - } - return nil - } -} -kt1cx_assert :: proc(kt: $type / KT1CX) { - slice_assert(kt.table) -} -kt1cx_byte :: proc(kt: $type / KT1CX) -> KT1CX_Byte { return { slice( transmute([^]byte) cursor(kt.table), len(kt.table)) } } diff --git a/code2/grime/key_table_1_layer_linear.odin b/code2/grime/key_table_1_layer_linear.odin deleted file mode 100644 index 8b8c15a..0000000 --- a/code2/grime/key_table_1_layer_linear.odin +++ /dev/null @@ -1,48 +0,0 @@ -package grime - -/* -Key Table 1-Layer Linear (KT1L) -*/ - -KT1L_Slot :: struct($Type: typeid) { - key: u64, - value: Type, -} -KT1L_Meta :: struct { - slot_size: int, - kt_value_offset: int, - type_width: int, - type: typeid, -} -kt1l_populate_slice_a2_Slice_Byte :: proc(kt: ^[]byte, backing: AllocatorInfo, values: []byte, num_values: int, m: KT1L_Meta) { - assert(kt != nil) - if num_values == 0 { return } - table_size_bytes := num_values * int(m.slot_size) - kt^, _ = mem_alloc(table_size_bytes, ainfo = transmute(Odin_Allocator) backing) - slice_assert(kt ^) - kt_raw : SliceByte = transmute(SliceByte) kt^ - for id in 0 ..< num_values { - slot_offset := id * m.slot_size // slot id - slot_cursor := kt_raw.data[slot_offset:] // slots[id] type: KT1L_ - // slot_key := transmute(^u64) slot_cursor // slots[id].key type: U64 - // slot_value := slice(slot_cursor[m.kt_value_offset:], m.type_width) // slots[id].value type: - a2_offset := id * m.type_width * 2 // a2 entry id - a2_cursor := cursor(values)[a2_offset:] // a2_entries[id] type: A2_ - // a2_key := (transmute(^[]byte) a2_cursor) ^ // a2_entries[id].key type: - // a2_value := slice(a2_cursor[m.type_width:], m.type_width) // a2_entries[id].value type: - mem_copy_non_overlapping(slot_cursor[m.kt_value_offset:], a2_cursor[m.type_width:], m.type_width) // slots[id].value = a2_entries[id].value - (transmute([^]u64) slot_cursor)[0] = 0; - hash64_djb8(transmute(^u64) slot_cursor, (transmute(^[]byte) a2_cursor) ^) // slots[id].key = hash64_djb8(a2_entries[id].key) - } - kt_raw.len = num_values -} -kt1l_populate_slice_a2 :: proc($Type: typeid, kt: ^[]KT1L_Slot(Type), backing: AllocatorInfo, values: [][2]Type) { - assert(kt != nil) - values_bytes := slice(transmute([^]u8) raw_data(values), len(values) * size_of([2]Type)) - kt1l_populate_slice_a2_Slice_Byte(transmute(^[]byte) kt, backing, values_bytes, len(values), { - slot_size = size_of(KT1L_Slot(Type)), - kt_value_offset = offset_of(KT1L_Slot(Type), value), - type_width = size_of(Type), - type = Type, - }) -} diff --git a/code2/grime/key_table_chained_chunked_cells.odin b/code2/grime/key_table_chained_chunked_cells.odin new file mode 100644 index 0000000..4f9efdf --- /dev/null +++ b/code2/grime/key_table_chained_chunked_cells.odin @@ -0,0 +1,196 @@ +package grime + +import "base:intrinsics" + +/* +Key Table Chained-Chunked-Cells + +Table has a cell with a user-specified depth. Each cell will be a linear search if the first slot is occupied. +Table allocated cells are looked up by hash. +If a cell is exhausted additional are allocated singly-chained reporting to the user when it does with a "cell_overflow" counter. +Slots track occupacy with a tombstone (occupied signal). + +If the table ever needs to change its size, it should be a wipe and full traversal of the arena holding the values.. +or maybe a wipe of that arena as it may no longer be accessible. + +Has a likely-hood of having cache misses (based on reading other impls about these kind of tables). +Odin's hash-map or Jai's are designed with open-addressing and prevent that. +Intended to be wrapped in parent interface (such as a string cache). Keys are hashed by the table's user. +The table is not intended to directly store the type's value in it's slots (expects the slot value to be some sort of reference). +The value should be stored in an arena. + +Could be upgraded two a X-layer, not sure if its ever viable. +Would essentially be segmenting the hash to address a multi-layered table lookup. +Where one table leads to another hash resolving id for a subtable with linear search of cells after. +*/ + +KTCX_Slot :: struct($type: typeid) { + value: type, + key: u64, + occupied: b32, +} +KTCX_Cell :: struct($type: typeid, $depth: int) { + slots: [depth]KTCX_Slot(type), + next: ^KTCX_Cell(type, depth), +} +KTCX :: struct($cell: typeid) { + table: []cell, + cell_overflow: int, +} +KTCX_Byte_Slot :: struct { + key: u64, + occupied: b32, +} +KTCX_Byte_Cell :: struct { + next: ^byte, +} +KTCX_Byte :: struct { + table: []byte, + cell_overflow: int, +} +KTCX_ByteMeta :: struct { + slot_size: int, + slot_key_offset: uintptr, + cell_next_offset: uintptr, + cell_depth: int, + cell_size: int, + type_width: int, + type: typeid, +} +KTCX_Info :: struct { + table_size: int, + slot_size: int, + slot_key_offset: uintptr, + cell_next_offset: uintptr, + cell_depth: int, + cell_size: int, + type_width: int, + type: typeid, +} +ktcx_byte :: #force_inline proc "contextless" (kt: $type / KTCX) -> KTCX_Byte { return { slice( transmute([^]byte) cursor(kt.table), len(kt.table)) } } + +ktcx_init_byte :: proc(result: ^KTCX_Byte, tbl_backing: Odin_Allocator, m: KTCX_Info) { + assert(result != nil) + assert(tbl_backing.procedure != nil) + assert(m.cell_depth > 0) + assert(m.table_size >= 4 * Kilo) + assert(m.type_width > 0) + table_raw, error := mem_alloc(m.table_size * m.cell_size, ainfo = tbl_backing) + assert(error == .None); slice_assert(transmute([]byte) table_raw) + (transmute(^SliceByte) & table_raw).len = m.table_size + result.table = table_raw +} +ktcx_clear :: proc(kt: KTCX_Byte, m: KTCX_ByteMeta) { + cell_cursor := cursor(kt.table) + table_len := len(kt.table) * m.cell_size + for ; cell_cursor != end(kt.table); cell_cursor = cell_cursor[m.cell_size:] // for cell, cell_id in kt.table.cells + { + slots := SliceByte { cell_cursor, m.cell_depth * m.slot_size } // slots = cell.slots + slot_cursor := slots.data + for;; { + slot := slice(slot_cursor, m.slot_size) // slot = slots[slot_id] + zero(slot) // slot = {} + if slot_cursor == end(slots) { // if slot == end(slot) + next := slot_cursor[m.cell_next_offset:] // next = kt.table.cells[cell_id + 1] + if next != nil { // if next != nil + slots.data = next // slots = next.slots + slot_cursor = next + continue + } + } + slot_cursor = slot_cursor[m.slot_size:] // slot = slots[slot_id + 1] + } + } +} +ktcx_slot_id :: #force_inline proc "contextless" (table: []byte, key: u64) -> u64 { + return key % u64(len(table)) +} +ktcx_get :: proc(kt: KTCX_Byte, key: u64, m: KTCX_ByteMeta) -> ^byte { + hash_index := key % u64(len(kt.table)) // ktcx_slot_id + cell_offset := uintptr(hash_index) * uintptr(m.cell_size) + cell_cursor := cursor(kt.table)[cell_offset:] // cell_id = 0 + { + slots := slice(cell_cursor, m.cell_depth * m.slot_size) // slots = cell[cell_id].slots + slot_cursor := cell_cursor // slot_id = 0 + for;; + { + slot := transmute(^KTCX_Byte_Slot) slot_cursor[m.slot_key_offset:] // slot = cell[slot_id] + if slot.occupied && slot.key == key { + return cast(^byte) slot_cursor + } + if slot_cursor == end(slots) + { + cell_next := cell_cursor[m.cell_next_offset:] // cell.next + if cell_next != nil { + slots = slice(cell_next, len(slots)) // slots = cell.next + slot_cursor = cell_next + cell_cursor = cell_next // cell = cell.next + continue + } + else { + return nil + } + } + slot_cursor = slot_cursor[m.slot_size:] + } + } +} +ktcx_set :: proc(kt: ^KTCX_Byte, key: u64, value: []byte, backing_cells: Odin_Allocator, m: KTCX_ByteMeta) -> ^byte { + hash_index := key % u64(len(kt.table)) // ktcx_slot_id + cell_offset := uintptr(hash_index) * uintptr(m.cell_size) + cell_cursor := cursor(kt.table)[cell_offset:] // KTCX_Cell(Type) cell = kt.table[hash_index] + { + slots := SliceByte {cell_cursor, m.cell_depth * m.slot_size} // cell.slots + slot_cursor := slots.data + for ;; + { + slot := transmute(^KTCX_Byte_Slot) slot_cursor[m.slot_key_offset:] + if slot.occupied == false { + slot.occupied = true + slot.key = key + return cast(^byte) slot_cursor + } + else if slot.key == key { + return cast(^byte) slot_cursor + } + if slot_cursor == end(slots) { + curr_cell := transmute(^KTCX_Byte_Cell) (uintptr(cell_cursor) + m.cell_next_offset) // curr_cell = cell + if curr_cell != nil { + slots.data = curr_cell.next + slot_cursor = curr_cell.next + cell_cursor = curr_cell.next + continue + } + else { + ensure(false, "Exhausted a cell. Increase the table size?") + new_cell, _ := mem_alloc(m.cell_size, ainfo = backing_cells) + curr_cell.next = raw_data(new_cell) + slot = transmute(^KTCX_Byte_Slot) cursor(new_cell)[m.slot_key_offset:] + slot.occupied = true + slot.key = key + kt.cell_overflow += 1 + return raw_data(new_cell) + } + } + slot_cursor = slot_cursor[m.slot_size:] + } + return nil + } +} + +// Type aware wrappers + +ktcx_init :: #force_inline proc(table_size: int, tbl_backing: Odin_Allocator, + kt: ^$kt_type / KTCX(KTCX_Cell(KTCX_Slot($Type), $Depth)) +){ + ktcx_init_byte(transmute(^KTCX_Byte) kt, tbl_backing, { + table_size = table_size, + slot_size = size_of(KTCX_Slot(Type)), + slot_key_offset = offset_of(KTCX_Slot(Type), key), + cell_next_offset = offset_of(KTCX_Cell(Type, Depth), next), + cell_depth = Depth, + cell_size = size_of(KTCX_Cell(Type, Depth)), + type_width = size_of(Type), + type = Type, + }) +} diff --git a/code2/grime/key_table_inear.odin b/code2/grime/key_table_inear.odin new file mode 100644 index 0000000..ecc6146 --- /dev/null +++ b/code2/grime/key_table_inear.odin @@ -0,0 +1,37 @@ +package grime + +/* +Key Table 1-Layer Linear (KT1L) + +Mainly intended for doing linear lookup of key-paried values. IE: Arg value parsing with label ids. +The table is built in one go from the key-value pairs. The default populate slice_a2 has the key and value as the same type. +*/ + +KTL_Slot :: struct($Type: typeid) { + key: u64, + value: Type, +} +KTL_Meta :: struct { + slot_size: int, + kt_value_offset: int, + type_width: int, + type: typeid, +} + +ktl_get :: #force_inline proc(kt: []KTL_Slot($Type), key: u64) -> ^Type { + for & slot in kt { if key == slot.key do return & slot.value; } + return nil +} + +// Unique populator for key-value pair strings + +ktl_populate_slice_a2_str :: #force_inline proc (kt: ^[]KTL_Slot(string), backing: Odin_Allocator, values: [][2]string) { + assert(kt != nil) + if len(values) == 0 { return } + raw_bytes, error := mem_alloc(size_of(KTL_Slot(string)) * len(values), ainfo = backing); assert(error == .None); + kt^ = slice( transmute([^]KTL_Slot(string)) cursor(raw_bytes), len(raw_bytes) / size_of(KTL_Slot(string)) ) + for id in 0 ..< len(values) { + mem_copy_non_overlapping(& kt[id].value, & values[id][1], size_of(string)) + hash64_fnv1a(& kt[id].key, transmute([]byte) values[id][0]) + } +} diff --git a/code2/grime/key_table.odin b/code2/grime/key_table_wip.odin similarity index 96% rename from code2/grime/key_table.odin rename to code2/grime/key_table_wip.odin index d68cdf0..e8c0d9b 100644 --- a/code2/grime/key_table.odin +++ b/code2/grime/key_table_wip.odin @@ -1,13 +1,14 @@ package grime /* -Hassh Table based on John's Jai & Sean Barrett's +Hash Table based on John's Jai & Sean Barrett's I don't like the table definition cntaining the allocator, hash or compare procedure to be used. So it has been stripped and instead applied on procedure site, the parent container or is responsible for tracking that. TODO(Ed): Resolve appropriate Key-Table term for it. +TODO(Ed): Complete this later if we actually have issues with KT1CX or Odin's map. */ KT_Slot :: struct( diff --git a/code2/grime/memory.odin b/code2/grime/memory.odin index e2aaa61..ea567e7 100644 --- a/code2/grime/memory.odin +++ b/code2/grime/memory.odin @@ -5,6 +5,23 @@ Mega :: Kilo * 1024 Giga :: Mega * 1024 Tera :: Giga * 1024 +// Provides the nearest prime number value for the given capacity +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] +} + raw_cursor :: #force_inline proc "contextless" (ptr: rawptr) -> [^]byte { return transmute([^]byte) ptr } ptr_cursor :: #force_inline proc "contextless" (ptr: ^$Type) -> [^]Type { return transmute([^]Type) ptr } diff --git a/code2/grime/memory_tracker.odin b/code2/grime/memory_tracker.odin index 9fd8c45..0098955 100644 --- a/code2/grime/memory_tracker.odin +++ b/code2/grime/memory_tracker.odin @@ -17,7 +17,7 @@ MemoryTracker :: struct { entries : Array(MemoryTrackerEntry), } -Track_Memory :: true +Track_Memory :: false @(disabled = Track_Memory == false) memtracker_clear :: proc (tracker: MemoryTracker) { diff --git a/code2/grime/string_cache.odin b/code2/grime/string_cache.odin index 9bf56f9..74c44c8 100644 --- a/code2/grime/string_cache.odin +++ b/code2/grime/string_cache.odin @@ -7,18 +7,20 @@ StrKey_U4 :: struct { StrKT_U4_Cell_Depth :: 4 -StrKT_U4_Slot :: KT1CX_Slot(StrKey_U4) -StrKT_U4_Cell :: KT1CX_Cell(StrKT_U4_Slot, 4) -StrKT_U4_Table :: KT1CX(StrKT_U4_Cell) +StrKT_U4_Slot :: KTCX_Slot(StrKey_U4) +StrKT_U4_Cell :: KTCX_Cell(StrKT_U4_Slot, 4) +StrKT_U4_Table :: KTCX(StrKT_U4_Cell) VStrKT_U4 :: struct { - varena: VArena, // Backed by growing vmem - entries: StrKT_U4_Table + varena: VArena, // Backed by growing vmem + kt: StrKT_U4_Table, } -vstrkt_u4_init :: proc(varena: ^VArena) -> (cache: ^VStrKT_U4) +vstrkt_u4_init :: proc(varena: ^VArena, capacity: int, cache: ^VStrKT_U4) { - return nil + capacity := cast(int) closest_prime(cast(uint) capacity) + ktcx_init(capacity, varena_allocator(varena), &cache.kt) + return } vstrkt_u4_intern :: proc(cache: ^VStrKT_U4) -> StrKey_U4