From 57d51fc7b120d89f6ff473ba7f6a9ce602656ede Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 30 Dec 2024 12:22:26 -0500 Subject: [PATCH] Began to reduce direct referencing of mutable state struct --- Readme.md | 6 +- code/sectr/engine/client_api.odin | 72 +++++++++---------- .../engine/client_api_sokol_callbacks.odin | 7 +- code/sectr/engine/render.odin | 17 ++--- code/sectr/engine/update.odin | 7 +- code/sectr/grime/mappings.odin | 14 +++- code/sectr/input/events.odin | 16 +++++ code/sectr/ui/widgets.odin | 17 +++-- 8 files changed, 90 insertions(+), 66 deletions(-) diff --git a/Readme.md b/Readme.md index d576c4b..bf3f948 100644 --- a/Readme.md +++ b/Readme.md @@ -23,7 +23,11 @@ The dependencies are: * I added support for 'monlithic packages' or 'uniform-across-subdirectories packages'. It allows me to organize the main package with sub-directories. * Odin repo's base, core, and vendor(raylib) libaries * An ini parser -* backtrace +* backtrace (not used yet) +* freetype (not used yet) +* harfbuzz +* sokol +* sokol-tools * Powershell (if you want to use my build scripts) Major 'codebase modules': diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index 60f288b..24986d3 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -271,11 +271,11 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } ) // font_squidgy_slimes = font_load( path_squidgy_slimes, 32.0, "Squidgy_Slime" ) - // path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } ) - // font_firacode = font_load( path_firacode, 16.0, "FiraCode" ) - - path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } ) - font_fira_cousine = font_load( path_fira_cousine, 16.0, "Fira Cousine" ) + path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } ) + font_firacode = font_load( path_firacode, 16.0, "FiraCode" ) + + // path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } ) + // font_fira_cousine = font_load( path_fira_cousine, 16.0, "Fira Cousine" ) // path_open_sans := strings.concatenate( { Path_Assets, "OpenSans-Regular.ttf" } ) // font_open_sans = font_load( path_open_sans, 16.0, "OpenSans" ) @@ -298,7 +298,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } ) // font_arial_unicode_ms = font_load( path_arial_unicode_ms, 16.0, "Arial_Unicode_MS" ) - default_font = font_fira_cousine + default_font = font_firacode log( "Default font loaded" ) } @@ -319,7 +319,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem } // Demo project setup - // TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know + // TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. if true { profile("project setup") @@ -499,21 +499,17 @@ tick :: proc( host_delta_time_ms : f64, host_delta_ns : Duration ) -> b32 // Lifted out of tick so that sokol_app_frame_callback can do it as well. tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 { - context.logger = to_odin_logger( & Memory_App.logger ) - state := get_state(); using state profile("Work frame") - + context.logger = to_odin_logger( & Memory_App.logger ) should_close : b32 // Setup Frame Slab - { - alloc_error : AllocatorError - frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, - allocator = frame_allocator(), - dbg_name = Frame_Slab_DBG_Name, - should_zero_buckets = true ) - verify( alloc_error == .None, "Failed to allocate frame slab" ) - } + alloc_error : AllocatorError + get_state().frame_slab, alloc_error = slab_init( & get_state().default_slab_policy, bucket_reserve_num = 0, + allocator = frame_allocator(), + dbg_name = Frame_Slab_DBG_Name, + should_zero_buckets = true ) + verify( alloc_error == .None, "Failed to allocate frame slab" ) // The policy for the work tick is that the default allocator is the frame's slab. // Transient's is the temp allocator. @@ -522,6 +518,9 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 // rl.PollInputEvents() + config := app_config() + debug := & get_state().debug + debug.draw_ui_box_bounds_points = false debug.draw_ui_padding_bounds = false debug.draw_ui_content_bounds = false @@ -535,7 +534,7 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 sokol_width := sokol_app.widthf() sokol_height := sokol_app.heightf() - window := & state.app_window + window := & get_state().app_window // if int(window.extent.x) != int(sokol_width) || int(window.extent.y) != int(sokol_height) { window.resized = true window.extent.x = sokol_width * 0.5 @@ -554,23 +553,24 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ms : f64, host_delta_ns : Duration, can_sleep := true ) { profile(#procedure) - state := get_state(); using state + config := app_config() + frametime := & get_state().frametime context.allocator = frame_slab_allocator() context.temp_allocator = transient_allocator() // profile("Client tick timing processing") - frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS - sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS + frametime.target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS + sub_ms_granularity_required := frametime.target_ms <= Frametime_High_Perf_Threshold_MS - frametime_delta_ns = time.tick_lap_time( client_tick ) - frametime_delta_ms = duration_ms( frametime_delta_ns ) - frametime_delta_seconds = duration_seconds( host_delta_ns ) - frametime_elapsed_ms = frametime_delta_ms + host_delta_time_ms + frametime.delta_ns = time.tick_lap_time( client_tick ) + frametime.delta_ms = duration_ms( frametime.delta_ns ) + frametime.delta_seconds = duration_seconds( host_delta_ns ) + frametime.elapsed_ms = frametime.delta_ms + host_delta_time_ms - if frametime_elapsed_ms < frametime_target_ms + if frametime.elapsed_ms < frametime.target_ms { - sleep_ms := frametime_target_ms - frametime_elapsed_ms + sleep_ms := frametime.target_ms - frametime.elapsed_ms pre_sleep_tick := time.tick_now() if can_sleep && sleep_ms > 0 { @@ -585,24 +585,24 @@ tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ // log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms )) } - frametime_elapsed_ms += sleep_delta_ms - for ; frametime_elapsed_ms < frametime_target_ms; { + frametime.elapsed_ms += sleep_delta_ms + for ; frametime.elapsed_ms < frametime.target_ms; { sleep_delta_ns = time.tick_lap_time( & pre_sleep_tick) sleep_delta_ms = duration_ms( sleep_delta_ns ) - frametime_elapsed_ms += sleep_delta_ms + frametime.elapsed_ms += sleep_delta_ms } } config.timing_fps_moving_avg_alpha = 0.99 - frametime_avg_ms = mov_avg_exp( f64(config.timing_fps_moving_avg_alpha), frametime_elapsed_ms, frametime_avg_ms ) - fps_avg = 1 / (frametime_avg_ms * MS_To_S) + frametime.avg_ms = mov_avg_exp( f64(config.timing_fps_moving_avg_alpha), frametime.elapsed_ms, frametime.avg_ms ) + frametime.fps_avg = 1 / (frametime.avg_ms * MS_To_S) - if frametime_elapsed_ms > 60.0 { - log( str_fmt("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning ) + if frametime.elapsed_ms > 60.0 { + log( str_fmt("Big tick! %v ms", frametime.elapsed_ms), LogLevel.Warning ) } - frame += 1 + frametime.current_frame += 1 } @export diff --git a/code/sectr/engine/client_api_sokol_callbacks.odin b/code/sectr/engine/client_api_sokol_callbacks.odin index f4afd28..ace4d9d 100644 --- a/code/sectr/engine/client_api_sokol_callbacks.odin +++ b/code/sectr/engine/client_api_sokol_callbacks.odin @@ -17,8 +17,8 @@ sokol_app_init_callback :: proc "c" () { // So this will only get called during window pan or resize events (on Win32 at least) sokol_app_frame_callback :: proc "c" () { - context = get_state().sokol_context profile(#procedure) + context = get_state().sokol_context state := get_state() should_close : b32 @@ -99,14 +99,13 @@ sokol_app_log_callback :: proc "c" ( // TODO(Ed): Does this need to be queued to a separate thread? sokol_app_event_callback :: proc "c" (sokol_event : ^sokol_app.Event) { - state := get_state(); using state - context = sokol_context + context = get_state().sokol_context event : InputEvent using event _sokol_frame_id = sokol_event.frame_count - frame_id = frame + frame_id = get_frametime().current_frame mouse.pos = { sokol_event.mouse_x, sokol_event.mouse_y } mouse.delta = { sokol_event.mouse_dx, sokol_event.mouse_dy } diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index 62b03a2..60f7bea 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -22,7 +22,7 @@ RenderState :: struct { render :: proc() { profile(#procedure) - state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack + state := get_state(); using state // TODO(Ed): Remove mutable access to to entire state. screen_extent := app_window.extent screen_size := app_window.extent * 2 @@ -152,7 +152,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color := Color_White, font : FontID = Font_Default ) { - state := get_state(); using state + state := get_state(); using state // TODO(Ed): Remove this state getter. Get default font properly. if len( content ) == 0 do return font := font @@ -187,11 +187,12 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State } if true { + frametime := get_frametime() debug_text( "Screen Width : %v", screen_size.x ) debug_text( "Screen Height: %v", screen_size.y ) - debug_text( "frametime_target_ms : %f ms", frametime_target_ms ) - debug_text( "frametime (work) : %0.3f ms", frametime_delta_ms ) - debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms ) + debug_text( "frametime_target_ms : %f ms", frametime.target_ms ) + debug_text( "frametime (work) : %0.3f ms", frametime.delta_ms ) + debug_text( "frametime_last_elapsed_ms : %f ms", frametime.elapsed_ms ) } if replay.mode == ReplayMode.Record { debug_text( "Recording Input") @@ -450,6 +451,7 @@ render_text_layer :: proc( screen_extent : Vec2, ve_ctx : ^ve.Context, render : render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil ) { + // TODO(Ed): Make a debug getter. debug := get_state().debug default_font := get_state().default_font @@ -801,8 +803,6 @@ draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, po width := app_window.extent.x * 2 height := app_window.extent.y * 2 - // TODO(Ed): Review doing double scaling on the text... - ve_id, resolved_size := font_provider_resolve_draw_id( id, size * config.font_size_screen_scalar ) color_norm := normalize_rgba8(color) @@ -825,7 +825,7 @@ draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, draw_text_string_pos_extent_zoomed :: proc( content : string, id : FontID, size : f32, pos : Vec2, cam : Camera, color := Color_White ) { profile(#procedure) - state := get_state(); using state + state := get_state(); using state // TODO(Ed): Remove usage of direct access to entire mutable state. cam_offset := Vec2 { cam.position.x, @@ -871,6 +871,7 @@ draw_text_string_pos_extent_zoomed :: proc( content : string, id : FontID, size render_flush_gp :: #force_inline proc() { profile(#procedure) + // TODO(Ed): Perfer a non-mutable get to the pass. gfx.begin_pass( gfx.Pass { action = get_state().render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) gp.flush() gfx.end_pass() diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index ac66729..b7a397d 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -56,14 +56,11 @@ poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState ) cam_mouse_pan = mouse.right.ended_down && ! pressed(mouse.right) } -frametime_delta32 :: #force_inline proc "contextless" () -> f32 { - return cast(f32) get_state().frametime_avg_ms -} - //TODO(Ed): Just use avg delta not this. update :: proc( delta_time : f64 ) -> b32 { profile(#procedure) + // TODO(Ed): Remove usage of mutable reference to state here. state := get_state(); using state replay := & Memory_App.replay workspace := & project.workspace @@ -259,7 +256,7 @@ update :: proc( delta_time : f64 ) -> b32 if zoom_delta != 0 { current_index := find_closest_zoom_index(cam.zoom, Digial_Zoom_Snap_Levels) - scroll_speed := max(1, abs(zoom_delta) * config.cam_zoom_scroll_delta_scale) // Adjust this factor to control sensitivity + scroll_speed := max( cast(f32) 1, abs(zoom_delta) * config.cam_zoom_scroll_delta_scale) // Adjust this factor to control sensitivity target_index := current_index if zoom_delta > 0 { diff --git a/code/sectr/grime/mappings.odin b/code/sectr/grime/mappings.odin index 03aba9e..499b781 100644 --- a/code/sectr/grime/mappings.odin +++ b/code/sectr/grime/mappings.odin @@ -428,9 +428,17 @@ make :: proc { make_multi_pointer, } -// measure_text_size :: proc { -// measure_text_size_raylib, -// } +max :: proc { + linalg.max_single, + linalg.max_double, + linalg.max_triple, +} + +min :: proc { + linalg.min_single, + linalg.min_double, + linalg.min_triple, +} mov_avg_exp :: proc { mov_avg_exp_f32, diff --git a/code/sectr/input/events.odin b/code/sectr/input/events.odin index 3ba2a44..1e2825c 100644 --- a/code/sectr/input/events.odin +++ b/code/sectr/input/events.odin @@ -285,3 +285,19 @@ poll_input_events :: proc( input, prev_input : ^InputState, input_events : Input prev_frame = last_frame } + +input_event_iter :: #force_inline proc () -> RingBufferFixedIterator(InputEvent) { + return iterator_ringbuf_fixed( & get_state().input_events.events ) +} + +input_key_event_iter :: #force_inline proc() -> RingBufferFixedIterator(InputKeyEvent) { + return iterator_ringbuf_fixed( & get_state().input_events.key_events ) +} + +input_mouse_event_iter :: #force_inline proc() -> RingBufferFixedIterator(InputMouseEvent) { + return iterator_ringbuf_fixed( & get_state().input_events.mouse_events ) +} + +input_codes_pressed_slice :: #force_inline proc() -> []rune { + return to_slice( get_state().input_events.codes_pressed ) +} diff --git a/code/sectr/ui/widgets.odin b/code/sectr/ui/widgets.odin index e0fc9fe..5fcaea1 100644 --- a/code/sectr/ui/widgets.odin +++ b/code/sectr/ui/widgets.odin @@ -587,10 +587,9 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, policy : UI_TextInput_Policy = {} ) { - state := get_state() + // state := get_state() iter_next :: next - input := state.input - input_events := & get_state().input_events + input := get_input_state() ui := ui_context() text_input_box.box = ui_box_make( flags, label ) @@ -621,7 +620,7 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, // TODO(Ed): Abstract this to navigation bindings if btn_pressed(input.keyboard.left) { - editor_cursor_pos.x = max(0, editor_cursor_pos.x - 1) + editor_cursor_pos.x = max(editor_cursor_pos.x - 1, 0) } if btn_pressed(input.keyboard.right) { editor_cursor_pos.x = min(i32(input_str.num), editor_cursor_pos.x + 1) @@ -638,14 +637,14 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, // screen_ui.active = 0 // } - iter_obj := iterator( & input_events.key_events ); iter := & iter_obj + iter_obj := input_key_event_iter(); iter := & iter_obj for event := iter_next( iter ); event != nil; event = iter_next( iter ) { - if event.frame_id != state.frame do break + if event.frame_id != get_frametime().current_frame do break if event.key == .backspace && event.type == .Key_Pressed { if input_str.num > 0 { - editor_cursor_pos.x = max(0, editor_cursor_pos.x - 1) + editor_cursor_pos.x = max(editor_cursor_pos.x - 1, 0) remove_at( input_str, u64(editor_cursor_pos.x) ) break } @@ -657,7 +656,7 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, } decimal_detected := false - for code in to_slice(input_events.codes_pressed) + for code in input_codes_pressed_slice() { accept_digits := ! digits_only || '0' <= code && code <= '9' accept_decimal := ! disallow_decimal || ! decimal_detected && code =='.' @@ -680,7 +679,7 @@ ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, continue } } - clear( input_events.codes_pressed ) + clear( get_state().input_events.codes_pressed ) invalid_color := RGBA8 { 70, 40, 40, 255}