From f1edf1c43edba3c94f71166f3a1d86bbb3631c0f Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 8 Mar 2024 23:20:49 -0500 Subject: [PATCH] Added basic string interning --- code/api.odin | 10 ++-- code/env.odin | 7 +-- code/font_provider.odin | 1 + code/grime.odin | 5 ++ code/grime_slab_allocator.odin | 3 +- code/grime_string_interning.odin | 86 ++++++++++++++++++++++++++++++++ code/host/host.odin | 5 +- code/serialize.odin | 8 +-- code/text.odin | 29 ++++++++++- code/tick_render.odin | 2 +- code/tick_update.odin | 6 +-- code/ui.odin | 6 ++- code/ui_tests.odin | 1 - 13 files changed, 144 insertions(+), 25 deletions(-) diff --git a/code/api.odin b/code/api.odin index 5faf46c..966ef90 100644 --- a/code/api.odin +++ b/code/api.odin @@ -79,6 +79,8 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V verify( alloc_error == .None, "Failed to allocate the general slab allocator" ) } + string_cache = str_cache_init() + context.user_ptr = state input = & input_data[1] @@ -143,7 +145,7 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V // path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } ) // font_squidgy_slimes = font_load( path_squidgy_slimes, 24.0, "Squidgy_Slime" ) - path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" }, frame_allocator() ) + path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" }, transient_allocator() ) font_firacode = font_load( path_firacode, 24.0, "FiraCode" ) default_font = font_firacode log( "Default font loaded" ) @@ -152,9 +154,9 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V // Demo project setup { using project - path = "./" - name = "First Project" - workspace.name = "First Workspace" + path = str_intern("./") + name = str_intern( "First Project" ) + workspace.name = str_intern( "First Workspace" ) { using project.workspace cam = { diff --git a/code/env.odin b/code/env.odin index 4f16c9f..f6ae4de 100644 --- a/code/env.odin +++ b/code/env.odin @@ -133,6 +133,7 @@ AppConfig :: struct { State :: struct { general_slab : Slab, + string_cache : StringCache, font_provider_data : FontProviderData, @@ -190,8 +191,8 @@ ProjectConfig :: struct { } Project :: struct { - path : string, - name : string, + path : StringCached, + name : StringCached, config : ProjectConfig, codebase : CodeBase, @@ -209,7 +210,7 @@ Frame :: struct } Workspace :: struct { - name : string, + name : StringCached, cam : Camera, zoom_target : f32, diff --git a/code/font_provider.odin b/code/font_provider.odin index 6ffad7d..7dee121 100644 --- a/code/font_provider.odin +++ b/code/font_provider.odin @@ -160,6 +160,7 @@ font_load :: proc( path_file : string, rl.UnloadImage( atlas ) } + free_all( context.temp_allocator ) return { key, desired_id } } diff --git a/code/grime.odin b/code/grime.odin index 5af86c5..9f67e94 100644 --- a/code/grime.odin +++ b/code/grime.odin @@ -94,6 +94,11 @@ cm_to_pixels :: proc { range2_cm_to_pixels, } +draw_text :: proc { + draw_text_string, + draw_text_string_cached, +} + get_bounds :: proc { view_get_bounds, } diff --git a/code/grime_slab_allocator.odin b/code/grime_slab_allocator.odin index 8a219c3..db52f05 100644 --- a/code/grime_slab_allocator.odin +++ b/code/grime_slab_allocator.odin @@ -44,7 +44,7 @@ SlabPolicy :: StackFixed(SlabSizeClass, Slab_Max_Size_Classes) SlabHeader :: struct { backing : Allocator, - policy : SlabPolicy, + policy : SlabPolicy, // TODO(Ed) : Remove this, the policy can't be changed after its been set so its meaningless to have... pools : StackFixed(Pool, Slab_Max_Size_Classes), } @@ -222,7 +222,6 @@ slab_allocator_proc :: proc( alignment := uint(alignment) old_size := uint(old_size) - // TODO(Ed) : Compiler bug - Some of these are commented out until I finish resolving issues with the pool allocator switch mode { case .Alloc, .Alloc_Non_Zeroed: diff --git a/code/grime_string_interning.odin b/code/grime_string_interning.odin index 2432dbc..77d3d93 100644 --- a/code/grime_string_interning.odin +++ b/code/grime_string_interning.odin @@ -1 +1,87 @@ +/* +This is a quick and dirty string table. +IT uses the HMapZPL for the hashtable of strings, and the string's content is stored in a dedicated slab. + +Future Plans (IF needed for performance): +The goal is to eventually swap out the slab with possilby a dedicated growing vmem arena for the strings. +The table would be swapped with a table stored in the general slab and uses either linear probing or open addressing + +If linear probing, the hash node list per table bucket is store with the strigns in the same arena. +If open addressing, we just keep the open addressed array of node slots in the general slab (but hopefully better perf) +*/ package sectr + +import "core:mem" +import "core:slice" +import "core:strings" + +StringCached :: struct { + str : string, + runes : []rune, +} + +StringCache :: struct { + slab : Slab, + table : HMapZPL(StringCached), +} + +str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) { + alignment := uint(mem.DEFAULT_ALIGNMENT) + + policy : SlabPolicy + policy_ptr := & policy + push( policy_ptr, SlabSizeClass { 8 * Megabyte, 16, alignment }) + push( policy_ptr, SlabSizeClass { 8 * Megabyte, 32, alignment }) + push( policy_ptr, SlabSizeClass { 16 * Megabyte, 64, alignment }) + push( policy_ptr, SlabSizeClass { 16 * Megabyte, 128, alignment }) + push( policy_ptr, SlabSizeClass { 16 * Megabyte, 256, alignment }) + push( policy_ptr, SlabSizeClass { 16 * Megabyte, 512, alignment }) + push( policy_ptr, SlabSizeClass { 32 * Megabyte, 1 * Kilobyte, alignment }) + push( policy_ptr, SlabSizeClass { 32 * Megabyte, 4 * Kilobyte, alignment }) + push( policy_ptr, SlabSizeClass { 64 * Megabyte, 16 * Kilobyte, alignment }) + push( policy_ptr, SlabSizeClass { 64 * Megabyte, 32 * Kilobyte, alignment }) + // push( policy_ptr, SlabSizeClass { 64 * Megabyte, 64 * Kilobyte, alignment }) + // push( policy_ptr, SlabSizeClass { 64 * Megabyte, 128 * Kilobyte, alignment }) + // push( policy_ptr, SlabSizeClass { 64 * Megabyte, 256 * Kilobyte, alignment }) + // push( policy_ptr, SlabSizeClass { 64 * Megabyte, 512 * Kilobyte, alignment }) + // push( policy_ptr, SlabSizeClass { 64 * Megabyte, 1 * Megabyte, alignment }) + + header_size :: size_of( Slab ) + + alloc_error : AllocatorError + cache.slab, alloc_error = slab_init( & policy, allocator = persistent_allocator() ) + verify(alloc_error == .None, "Failed to initialize the string cache" ) + + cache.table, alloc_error = zpl_hmap_init_reserve( StringCached, general_slab_allocator(), 64 * Kilobyte ) + return +} + +// str_cache_intern_string :: proc( + // cache : ^StringCache, +str_intern :: proc( + content : string +) -> StringCached +{ + cache := get_state().string_cache + + key := u64( crc32( transmute([]byte) content )) + result := zpl_hmap_get( & cache.table, key ) + if result != nil { + return (result ^) + } + + length := len(content) + str_mem, alloc_error := slab_alloc( cache.slab, uint(length), uint(mem.DEFAULT_ALIGNMENT) ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) + + copy_non_overlapping( raw_data(str_mem), raw_data(content), length ) + + runes : []rune + runes, alloc_error = to_runes( content, slab_allocator(cache.slab) ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) + + result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } ) + verify( alloc_error == .None, "String cache had a backing allocator error" ) + + return (result ^) +} diff --git a/code/host/host.odin b/code/host/host.odin index fd7b756..8c80dcb 100644 --- a/code/host/host.odin +++ b/code/host/host.odin @@ -26,6 +26,7 @@ import "core:dynlib" import "core:io" import fmt_io "core:fmt" str_fmt :: fmt_io.printf + str_fmt_alloc :: fmt_io.aprintf str_fmt_tmp :: fmt_io.tprintf str_fmt_builder :: fmt_io.sbprintf import "core:log" @@ -251,11 +252,11 @@ main :: proc() } timestamp := str_fmt_tmp("%04d-%02d-%02d_%02d-%02d-%02d", year, month, day, hour, min, sec) - path_logger_finalized = str_clone( str_fmt_tmp( "%s/sectr_%v.log", Path_Logs, timestamp) ) + path_logger_finalized = str_fmt_alloc( "%s/sectr_%v.log", Path_Logs, timestamp) } logger : sectr.Logger - logger_init( & logger, "Sectr Host", str_fmt_tmp( "%s/sectr.log", Path_Logs ) ) + logger_init( & logger, "Sectr Host", str_fmt_alloc( "%s/sectr.log", Path_Logs ) ) context.logger = to_odin_logger( & logger ) { // Log System Context diff --git a/code/serialize.odin b/code/serialize.odin index b4d5309..0473dad 100644 --- a/code/serialize.odin +++ b/code/serialize.odin @@ -163,12 +163,12 @@ project_save :: proc( project : ^ Project, archive : ^ ArchiveData = nil ) } project_serialize( project, archive ) - if ! os.is_dir( project.path ) { - os.make_directory( project.path ) - verify( cast(b32) os.is_dir( project.path ), "Failed to create project path for saving" ) + if ! os.is_dir( project.path.str ) { + os.make_directory( project.path.str ) + verify( cast(b32) os.is_dir( project.path.str ), "Failed to create project path for saving" ) } - os.write_entire_file( str_tmp_from_any( project.path, project.name, ".sectr_proj", sep = ""), archive.data ) + os.write_entire_file( str_tmp_from_any( project.path.str, project.name.str, ".sectr_proj", sep = ""), archive.data ) } project_load :: proc( path : string, project : ^ Project, archive : ^ ArchiveData = nil ) diff --git a/code/text.odin b/code/text.odin index 5cfde3f..4885808 100644 --- a/code/text.odin +++ b/code/text.odin @@ -33,7 +33,7 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co tint = color ); } -debug_draw_text_world :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) +draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) { state := get_state(); using state @@ -62,6 +62,33 @@ debug_draw_text_world :: proc( content : string, pos : Vec2, size : f32, color : tint = color ); } +draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) { + state := get_state(); using state + + if len( content.str ) == 0 { + return + } + font := font + if font.key == Font_Default.key { + // if len(font) == 0 { + font = default_font + } + pos := world_to_screen_pos(pos) + + px_size := size + zoom_adjust := px_size * project.workspace.cam.zoom + + runes := content.runes + + rl_font := to_rl_Font(font, zoom_adjust ) + rl.DrawTextCodepoints( rl_font, + raw_data(runes), cast(i32) len(runes), + position = transmute(rl.Vector2) pos, + fontSize = px_size, + spacing = 0.0, + tint = color ); +} + // Raylib's equivalent doesn't take a length for the string (making it a pain in the ass) // So this is a 1:1 copy except it takes Odin strings measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> AreaSize diff --git a/code/tick_render.odin b/code/tick_render.odin index 93cef7a..1b04112 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -88,7 +88,7 @@ render_mode_2d :: proc() rl.BeginMode2D( project.workspace.cam ) - debug_draw_text_world( "This is text in world space", { 0, 200 }, 16.0 ) + draw_text( "This is text in world space", { 0, 200 }, 16.0 ) ImguiRender: { diff --git a/code/tick_update.odin b/code/tick_update.odin index adc8617..3eefc7b 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -231,14 +231,10 @@ update :: proc( delta_time : f64 ) -> b32 } ui_set_layout( default_layout ) - // First Demo - Test_HoverNClick :: false - Test_Draggable :: true - // test_hover_n_click() // test_draggable() - + } //endregion Imgui Tick diff --git a/code/ui.odin b/code/ui.odin index 75e38a2..15f5d15 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -223,7 +223,9 @@ UI_TextAlign :: enum u32 { UI_Box :: struct { // Cache ID key : UI_Key, - label : string, + // label : string, + label : StringCached, + text : StringCached, // Regenerated per frame. using links : DLL_NodeFull( UI_Box ), // first, last, prev, next @@ -354,7 +356,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) else { box : UI_Box box.key = key - box.label = label + box.label = str_intern( label ) set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, box ) } diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 4794f13..39235cf 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -24,7 +24,6 @@ test_draggable :: proc() ui := ui_context draggable := ui_widget( "Draggable Box!", UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus } ) - if draggable.first_frame { debug.draggable_box_pos = draggable.style.layout.pos debug.draggable_box_size = draggable.style.layout.size