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.
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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 ) | ||||
| } | ||||
|   | ||||
| @@ -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 ) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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 } ) | ||||
|   | ||||
							
								
								
									
										126
									
								
								code/host/memory_windows.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								code/host/memory_windows.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user