thinking about key tables...
This commit is contained in:
46
Readme.md
46
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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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)) } }
|
||||
@@ -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_<Type>
|
||||
// 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: <Type>
|
||||
a2_offset := id * m.type_width * 2 // a2 entry id
|
||||
a2_cursor := cursor(values)[a2_offset:] // a2_entries[id] type: A2_<Type>
|
||||
// a2_key := (transmute(^[]byte) a2_cursor) ^ // a2_entries[id].key type: <Type>
|
||||
// a2_value := slice(a2_cursor[m.type_width:], m.type_width) // a2_entries[id].value type: <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,
|
||||
})
|
||||
}
|
||||
196
code2/grime/key_table_chained_chunked_cells.odin
Normal file
196
code2/grime/key_table_chained_chunked_cells.odin
Normal file
@@ -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,
|
||||
})
|
||||
}
|
||||
37
code2/grime/key_table_inear.odin
Normal file
37
code2/grime/key_table_inear.odin
Normal file
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ MemoryTracker :: struct {
|
||||
entries : Array(MemoryTrackerEntry),
|
||||
}
|
||||
|
||||
Track_Memory :: true
|
||||
Track_Memory :: false
|
||||
|
||||
@(disabled = Track_Memory == false)
|
||||
memtracker_clear :: proc (tracker: MemoryTracker) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user