From 84d9675a272ef57c794b1f774d69be5f566facca Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 8 Feb 2024 16:05:15 -0500 Subject: [PATCH] Got replay working Doesn't work across app runs, unlike handmade the crux here is raylib. Even if we did raylibs features ourselves there would still be an issue of restoring the gpu memory state. So in order to have replays work across app runs is proper state serialization. I'll leave that for later and focus on the next core features. --- code/api.odin | 60 ++++++++++------ code/env.odin | 47 ++++++++----- code/filesystem.odin | 11 +++ code/host/host.odin | 11 ++- code/host/memory_windows.odin | 126 ++++++++++++++++++++++++++++++++++ code/input.odin | 12 +++- code/math.odin | 8 +++ 7 files changed, 231 insertions(+), 44 deletions(-) create mode 100644 code/host/memory_windows.odin diff --git a/code/api.odin b/code/api.odin index 136a00e..b1e5b4c 100644 --- a/code/api.odin +++ b/code/api.odin @@ -37,6 +37,9 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8 ) using memory; block := live_mem.curr_block + live = live_mem + snapshot = snapshot_mem + persistent_slice := slice_ptr( block.base, memory_persistent_size ) transient_slice := slice_ptr( memory_after( persistent_slice), memory_trans_temp_size ) temp_slice := slice_ptr( memory_after( transient_slice), memory_trans_temp_size ) @@ -91,7 +94,7 @@ sectr_shutdown :: proc() // Replay { - os.close( state.replay.active_file ) + os.close( memory.replay.active_file ) } // Raylib @@ -107,6 +110,9 @@ reload :: proc( live_mem : virtual.Arena, snapshot_mem : []u8 ) using memory; block := live_mem.curr_block + live = live_mem + snapshot = snapshot_mem + persistent_slice := slice_ptr( block.base, memory_persistent_size ) transient_slice := slice_ptr( memory_after( persistent_slice), memory_trans_temp_size ) temp_slice := slice_ptr( memory_after( transient_slice), memory_trans_temp_size ) @@ -114,8 +120,6 @@ reload :: proc( live_mem : virtual.Arena, snapshot_mem : []u8 ) persistent = cast( ^TrackedAllocator ) & persistent_slice[0] transient = cast( ^TrackedAllocator ) & transient_slice[0] temp = cast( ^TrackedAllocator ) & temp_slice[0] - - snapshot = snapshot_mem } // TODO(Ed) : This lang really not have a fucking swap? @@ -126,12 +130,13 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) { @export update :: proc() -> b32 { - state := get_state(); using state + state := get_state(); using state + replay := & memory.replay state.input, state.input_prev = swap( state.input, state.input_prev ) poll_input( state.input_prev, state.input ) - debug_actions : DebugActions + debug_actions : DebugActions = {} poll_debug_actions( & debug_actions, state.input ) // Input Replay @@ -147,30 +152,40 @@ update :: proc() -> b32 } }} + DO_NOT_CONTINUE : b32 = false + if debug_actions.play_replay { switch replay.mode { case ReplayMode.Off : { - replay_playback_begin( Path_Input_Replay ) + if ! file_exists( Path_Input_Replay ) { + save_snapshot( & memory.snapshot[0] ) + replay_recording_begin( Path_Input_Replay ) + break + } + else { + load_snapshot( & memory.snapshot[0] ) + replay_playback_begin( Path_Input_Replay ) + break + } } case ReplayMode.Playback : { replay_playback_end() load_snapshot( & memory.snapshot[0] ) + break } case ReplayMode.Record : { - replay_recording_end( ) + replay_recording_end() load_snapshot( & memory.snapshot[0] ) replay_playback_begin( Path_Input_Replay ) + break } }} - if replay.loop_active - { - if replay.mode == ReplayMode.Record { - record_input( replay.active_file, input ) - } - else if replay.mode == ReplayMode.Playback { - play_input( replay.active_file, input ) - } + if replay.mode == ReplayMode.Record { + record_input( replay.active_file, input ) + } + else if replay.mode == ReplayMode.Playback { + play_input( replay.active_file, input ) } } @@ -178,6 +193,8 @@ update :: proc() -> b32 debug.mouse_vis = !debug.mouse_vis } + debug.mouse_pos.basis = { input.mouse.X, input.mouse.Y } + should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() return should_shutdown } @@ -185,7 +202,8 @@ update :: proc() -> b32 @export render :: proc() { - state := get_state(); using state + state := get_state(); using state + replay := & memory.replay rl.BeginDrawing() rl.ClearBackground( Color_BG ) @@ -213,17 +231,19 @@ render :: proc() draw_text( "Screen Width : %v", rl.GetScreenWidth () ) draw_text( "Screen Height: %v", rl.GetScreenHeight() ) - if pressed( input.keyboard.M ) { - draw_text( "M Prssed" ) + if replay.mode == ReplayMode.Record { + draw_text( "Recording Input") } - if pressed( input.keyboard.right_alt ) { - draw_text( "Alt Pressed") + if replay.mode == ReplayMode.Playback { + draw_text( "Replaying Input") } if debug.mouse_vis { width : f32 = 32 pos := debug.mouse_pos + draw_text( "Position: %v", rl.GetMousePosition() ) + mouse_rect : rl.Rectangle mouse_rect.x = pos.x - width/2 mouse_rect.y = pos.y - width/2 diff --git a/code/env.odin b/code/env.odin index 9c5a208..d974de8 100644 --- a/code/env.odin +++ b/code/env.odin @@ -19,7 +19,9 @@ Memory :: struct { snapshot : []u8, persistent : ^ TrackedAllocator, transient : ^ TrackedAllocator, - temp : ^ TrackedAllocator + temp : ^ TrackedAllocator, + + replay : ReplayState } State :: struct { @@ -28,7 +30,6 @@ State :: struct { input_prev : ^ InputState, input : ^ InputState, - replay : ReplayState, debug : DebugData, project : Project, @@ -66,7 +67,7 @@ DebugData :: struct { draw_debug_text_y : f32, mouse_vis : b32, - mouse_pos : vec3, + mouse_pos : vec2, } DebugActions :: struct { @@ -84,11 +85,11 @@ poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState ) using actions using input - base_replay_bind := pressed(keyboard.right_alt) && pressed( keyboard.L) - record_replay = base_replay_bind && pressed(keyboard.right_shift) - play_replay = base_replay_bind + base_replay_bind := keyboard.right_alt.ended_down && pressed( keyboard.L) + record_replay = base_replay_bind && keyboard.right_shift.ended_down + play_replay = base_replay_bind && ! keyboard.right_shift.ended_down - show_mouse_pos = pressed(keyboard.right_alt) && pressed(keyboard.M) + show_mouse_pos = keyboard.right_alt.ended_down && pressed(keyboard.M) } save_snapshot :: proc( snapshot : [^]u8 ) { @@ -115,14 +116,16 @@ ReplayState :: struct { replay_recording_begin :: proc( path : string ) { - result := os.remove( path ) - if ( result != os.ERROR_NONE ) - { - // TODO(Ed) : Setup a proper logging interface - fmt. printf( "Failed to delete replay file before beginning a new one" ) - runtime.debug_trap() - os. exit( -1 ) - // TODO(Ed) : Figure out the error code enums.. + if file_exists( path ) { + result := os.remove( path ) + if ( result != os.ERROR_NONE ) + { + // TODO(Ed) : Setup a proper logging interface + fmt. printf( "Failed to delete replay file before beginning a new one" ) + runtime.debug_trap() + os. exit( -1 ) + // TODO(Ed) : Figure out the error code enums.. + } } replay_file, open_error := os.open( path, os.O_RDWR | os.O_CREATE ) @@ -134,15 +137,18 @@ replay_recording_begin :: proc( path : string ) os. exit( -1 ) // TODO(Ed) : Figure out the error code enums.. } + os.seek( replay_file, 0, 0 ) - state := get_state(); using state + replay := & memory.replay replay.active_file = replay_file replay.mode = ReplayMode.Record } replay_recording_end :: proc() { - state := get_state(); using state + replay := & memory.replay replay.mode = ReplayMode.Off + + os.seek( replay.active_file, 0, 0 ) os.close( replay.active_file ) } @@ -167,14 +173,17 @@ replay_playback_begin :: proc( path : string ) // TODO(Ed) : Figure out the error code enums.. } // TODO(Ed): WE need to wrap any actions that can throw a fatal like this. Files need a grime wrap. + os.seek( replay_file, 0, 0 ) - state := get_state(); using state + replay := & memory.replay replay.active_file = replay_file replay.mode = ReplayMode.Playback } replay_playback_end :: proc() { - state := get_state(); using state + input := get_state().input + replay := & memory.replay replay.mode = ReplayMode.Off + os.seek( replay.active_file, 0, 0 ) os.close( replay.active_file ) } diff --git a/code/filesystem.odin b/code/filesystem.odin index d618c39..2517622 100644 --- a/code/filesystem.odin +++ b/code/filesystem.odin @@ -52,3 +52,14 @@ is_file_locked :: proc( file_path : string ) -> b32 { os.close(handle) return false } + +rewind :: proc ( file : os.Handle ) { + os.seek( file, 0, 0 ) +} + +read_looped :: proc ( file : os.Handle, data : []byte ) { + total_read, result_code := os.read( file, data ) + if result_code == os.ERROR_HANDLE_EOF { + rewind( file ) + } +} diff --git a/code/host/host.odin b/code/host/host.odin index 70370b8..fbf6f9f 100644 --- a/code/host/host.odin +++ b/code/host/host.odin @@ -62,7 +62,9 @@ setup_memory :: proc () -> VMemChunk // Setup the static arena for the entire application { - result := virtual.arena_init_static( & sectr_live, sectr.memory_chunk_size, sectr.memory_chunk_size ) + base_address : rawptr = transmute( rawptr) u64(Terabyte * 2) + + result := arena_init_static( & sectr_live, base_address, sectr.memory_chunk_size, sectr.memory_chunk_size ) if result != runtime.Allocator_Error.None { // TODO(Ed) : Setup a proper logging interface @@ -86,7 +88,12 @@ setup_memory :: proc () -> VMemChunk os. exit( -1 ) // TODO(Ed) : Figure out the error code enums.. } - file_resize( snapshot_file, sectr.memory_chunk_size ) + file_info, stat_code := os.stat( path_snapshot ) + { + if file_info.size != sectr.memory_chunk_size { + file_resize( snapshot_file, sectr.memory_chunk_size ) + } + } map_error : virtual.Map_File_Error sectr_snapshot, map_error = virtual.map_file_from_file_descriptor( uintptr(snapshot_file), { virtual.Map_File_Flag.Read, virtual.Map_File_Flag.Write } ) diff --git a/code/host/memory_windows.odin b/code/host/memory_windows.odin new file mode 100644 index 0000000..30ef446 --- /dev/null +++ b/code/host/memory_windows.odin @@ -0,0 +1,126 @@ +// TODO(Ed): Move this to the grime module when its made +// This was made becaause odin didn't expose the base_address param that virtual alloc allows. +package host + +import "core:mem" +import "core:mem/virtual" + +import win32 "core:sys/windows" + +@(private="file") +virtual_Platform_Memory_Block :: struct { + block: virtual.Memory_Block, + committed: uint, + reserved: uint, +} + +@(private="file", require_results) +align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { + result := size + align-1 + return result - result%align +} + +@(private="file") +win32_reserve :: proc "contextless" (base_address : rawptr, size: uint) -> (data: []byte, err: virtual.Allocator_Error) { + result := win32.VirtualAlloc(base_address, size, win32.MEM_RESERVE, win32.PAGE_READWRITE) + if result == nil { + err = .Out_Of_Memory + return + } + data = ([^]byte)(result)[:size] + return +} + +@(private="file") +platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint, base_address : rawptr) -> + (block: ^virtual_Platform_Memory_Block, err: virtual.Allocator_Error) +{ + to_commit, to_reserve := to_commit, to_reserve + to_reserve = max(to_commit, to_reserve) + + total_to_reserved := max(to_reserve, size_of( virtual_Platform_Memory_Block)) + to_commit = clamp(to_commit, size_of( virtual_Platform_Memory_Block), total_to_reserved) + + data := win32_reserve(base_address, total_to_reserved) or_return + virtual.commit(raw_data(data), to_commit) + + block = (^virtual_Platform_Memory_Block)(raw_data(data)) + block.committed = to_commit + block.reserved = to_reserve + return +} + +@(private="file") +platform_memory_commit :: proc "contextless" (block: ^virtual_Platform_Memory_Block, to_commit: uint) -> (err: virtual.Allocator_Error) { + if to_commit < block.committed { + return nil + } + if to_commit > block.reserved { + return .Out_Of_Memory + } + + virtual.commit(block, to_commit) or_return + block.committed = to_commit + return nil +} + +@(private="file", require_results) +memory_block_alloc :: proc(committed, reserved: uint, base_address : rawptr, + alignment : uint = 0, + flags : virtual.Memory_Block_Flags = {} +) -> (block: ^virtual.Memory_Block, err: virtual.Allocator_Error) +{ + page_size := virtual.DEFAULT_PAGE_SIZE + assert(mem.is_power_of_two(uintptr(page_size))) + + committed := committed + reserved := reserved + + committed = align_formula(committed, page_size) + reserved = align_formula(reserved, page_size) + committed = clamp(committed, 0, reserved) + + total_size := uint(reserved + max(alignment, size_of( virtual_Platform_Memory_Block))) + base_offset := uintptr(max(alignment, size_of( virtual_Platform_Memory_Block))) + protect_offset := uintptr(0) + + do_protection := false + if .Overflow_Protection in flags { // overflow protection + rounded_size := reserved + total_size = uint(rounded_size + 2*page_size) + base_offset = uintptr(page_size + rounded_size - uint(reserved)) + protect_offset = uintptr(page_size + rounded_size) + do_protection = true + } + + pmblock := platform_memory_alloc(0, total_size, base_address) or_return + + pmblock.block.base = ([^]byte)(pmblock)[base_offset:] + platform_memory_commit(pmblock, uint(base_offset) + committed) or_return + + // Should be zeroed + assert(pmblock.block.used == 0) + assert(pmblock.block.prev == nil) + if do_protection { + virtual.protect(([^]byte)(pmblock)[protect_offset:], page_size, virtual.Protect_No_Access) + } + + pmblock.block.committed = committed + pmblock.block.reserved = reserved + + return &pmblock.block, nil +} + +// This is the same as odin's virtual library, except I use my own allocation implementation to set the address space base. +@(require_results) +arena_init_static :: proc(arena: ^virtual.Arena, base_address : rawptr, + reserved : uint = virtual.DEFAULT_ARENA_STATIC_RESERVE_SIZE, + commit_size : uint = virtual.DEFAULT_ARENA_STATIC_COMMIT_SIZE +) -> (err: virtual.Allocator_Error) +{ + arena.kind = .Static + arena.curr_block = memory_block_alloc(commit_size, reserved, base_address, {}) or_return + arena.total_used = 0 + arena.total_reserved = arena.curr_block.reserved + return +} diff --git a/code/input.odin b/code/input.odin index e3e748a..6230177 100644 --- a/code/input.odin +++ b/code/input.odin @@ -299,7 +299,8 @@ poll_input :: proc( old, new : ^ InputState ) { check_range :: proc( old, new : ^ InputState, start, end : i32 ) { - for id := start; id < end; id += 1 { + for id := start; id < end; id += 1 + { // TODO(Ed) : LOOK OVER THIS... entry_old := & old.keyboard.keys[id - 1] entry_new := & new.keyboard.keys[id - 1] @@ -329,7 +330,8 @@ poll_input :: proc( old, new : ^ InputState ) // Mouse { // Process Buttons - for id : i32 = 0; id < i32(MouseBtn.count); id += 1 { + for id : i32 = 0; id < i32(MouseBtn.count); id += 1 + { old_btn := & old.mouse.btns[id] new_btn := & new.mouse.btns[id] @@ -353,7 +355,11 @@ record_input :: proc( replay_file : os.Handle, input : ^ InputState ) { play_input :: proc( replay_file : os.Handle, input : ^ InputState ) { raw_data := slice_ptr( transmute(^ byte) input, size_of(InputState) ) - os.read( replay_file, raw_data ) + total_read, result_code := os.read( replay_file, raw_data ) + if result_code == os.ERROR_HANDLE_EOF { + rewind( replay_file ) + load_snapshot( & memory.snapshot[0] ) + } } to_raylib_key :: proc ( key : i32 ) -> rl.KeyboardKey { diff --git a/code/math.odin b/code/math.odin index 6c6f79f..98e82dd 100644 --- a/code/math.odin +++ b/code/math.odin @@ -2,6 +2,14 @@ package sectr // TODO(Ed) : Evaluate if this is needed +vec2 :: vec2_f32 +vec2_f32 :: struct #raw_union { + basis : [2] f32, + using components : struct { + x, y : f32 + } +} + vec3 :: vec3_f32 vec3_f32 :: struct #raw_union { basis : [3] f32,