Hot reload works
This commit is contained in:
		| @@ -11,7 +11,7 @@ Path_Assets :: "../assets/" | ||||
|  | ||||
| ModuleAPI :: struct { | ||||
| 	lib         : dynlib.Library, | ||||
| 	load_time   : os.File_Time, | ||||
| 	write_time  : os.File_Time, | ||||
| 	lib_version : i32, | ||||
|  | ||||
| 	startup  : type_of( startup       ), | ||||
| @@ -33,22 +33,21 @@ memory : Memory | ||||
| startup :: proc( persistent, transient, temp : ^ mem.Arena ) | ||||
| { | ||||
| 	memory.persistent = persistent | ||||
| 	state            := cast(^State) memory.persistent; using state | ||||
|  | ||||
| 	// Anything allocated by default is considered transient. | ||||
| 	context.allocator      = mem.arena_allocator( transient ) | ||||
| 	context.temp_allocator = mem.arena_allocator( temp ) | ||||
|  | ||||
| 	state := cast(^State) memory.persistent | ||||
|  | ||||
| 	// Rough setup of window with rl stuff | ||||
| 	screen_width  : i32 = 1280 | ||||
| 	screen_height : i32 = 1000 | ||||
| 	screen_width  = 1280 | ||||
| 	screen_height = 1000 | ||||
| 	win_title     : cstring = "Sectr Prototype" | ||||
| 	rl.InitWindow( screen_width, screen_height, win_title ) | ||||
|  | ||||
| 	// Determining current monitor and setting the target frametime based on it.. | ||||
| 	monitor_id         := rl.GetCurrentMonitor() | ||||
| 	monitor_refresh_hz := rl.GetMonitorRefreshRate( monitor_id ) | ||||
| 	monitor_id         = rl.GetCurrentMonitor() | ||||
| 	monitor_refresh_hz = rl.GetMonitorRefreshRate( monitor_id ) | ||||
| 	rl.SetTargetFPS( monitor_refresh_hz ) | ||||
| 	fmt.println( "Set target FPS to: %v", monitor_refresh_hz ) | ||||
|  | ||||
| @@ -68,7 +67,11 @@ startup :: proc( persistent, transient, temp : ^ mem.Arena ) | ||||
| @export | ||||
| sectr_shutdown :: proc() | ||||
| { | ||||
| 	rl.UnloadFont( font_rec_mono_semicasual_reg ) | ||||
| 	if memory.persistent == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	state := cast( ^ State ) memory.persistent | ||||
| 	rl.UnloadFont( state.font_rec_mono_semicasual_reg ) | ||||
| 	rl.CloseWindow() | ||||
| } | ||||
|  | ||||
| @@ -77,21 +80,25 @@ reload :: proc( persistent, transient, temp : ^ mem.Arena ) | ||||
| { | ||||
| 	memory.persistent      = persistent | ||||
| 	memory.transient       = transient | ||||
| 	context.allocator      = mem.arena_allocator( persistent ) | ||||
| 	context.temp_allocator = mem.arena_allocator( transient ) | ||||
| 	memory.temp            = temp | ||||
| 	context.allocator      = mem.arena_allocator( transient ) | ||||
| 	context.temp_allocator = mem.arena_allocator( temp ) | ||||
| } | ||||
|  | ||||
| @export | ||||
| update :: proc() -> b32 | ||||
| { | ||||
| 	should_shutdown : b32 =  ! cast(b32) rl.WindowShouldClose() | ||||
| 	state := cast( ^ State ) memory.persistent | ||||
|  | ||||
| 	should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() | ||||
| 	return should_shutdown | ||||
| } | ||||
|  | ||||
| draw_text_y : f32 = 50 | ||||
| @export | ||||
| render :: proc() | ||||
| { | ||||
| 	state := cast( ^ State ) memory.persistent; using state | ||||
|  | ||||
| 	rl.BeginDrawing() | ||||
| 	rl.ClearBackground( Color_BG ) | ||||
| 	defer { | ||||
| @@ -102,21 +109,22 @@ render :: proc() | ||||
|  | ||||
| 	draw_text :: proc( format : string, args : ..any ) | ||||
| 	{ | ||||
| 		@static | ||||
| 		draw_text_scratch : [Kilobyte * 64]u8 | ||||
| 		if ( draw_text_y > 500 ) { | ||||
| 			draw_text_y = 50 | ||||
| 		@static draw_text_scratch : [Kilobyte * 64]u8 | ||||
|  | ||||
| 		state := cast( ^ State ) memory.persistent; using state | ||||
| 		if ( draw_debug_text_y > 800 ) { | ||||
| 			draw_debug_text_y = 50 | ||||
| 		} | ||||
|  | ||||
| 		content := fmt.bprintf( draw_text_scratch[:], format, ..args ) | ||||
| 		debug_text( content, 25, draw_text_y ) | ||||
| 		debug_text( content, 25, draw_debug_text_y ) | ||||
|  | ||||
| 		draw_text_y += 16 | ||||
| 		draw_debug_text_y += 16 | ||||
| 	} | ||||
|  | ||||
| 	draw_text( "Monitor      : %v", rl.GetMonitorName(0) ) | ||||
| 	draw_text( "Screen Width : %v", rl.GetScreenWidth() ) | ||||
| 	draw_text( "Screen Height: %v", rl.GetScreenHeight() ) | ||||
|  | ||||
| 	draw_text_y = 50 | ||||
| 	draw_debug_text_y = 50 | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,9 @@ State :: struct { | ||||
| 	engine_refresh_hz     : i32, | ||||
| 	engine_refresh_target : i32, | ||||
|  | ||||
| 	font_rec_mono_semicasual_reg : Font, | ||||
| 	default_font                 : Font, | ||||
|  | ||||
| 	draw_debug_text_y : f32 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,9 +4,9 @@ import "core:fmt" | ||||
| import "core:os" | ||||
| import "core:runtime" | ||||
|  | ||||
| copy_file_sync :: proc( path_src, path_dst: string ) -> bool | ||||
| copy_file_sync :: proc( path_src, path_dst: string ) -> b32 | ||||
| { | ||||
|     file_size : i64 | ||||
|   file_size : i64 | ||||
| 	{ | ||||
| 		path_info, result := os.stat( path_src, context.temp_allocator ) | ||||
| 		if result != os.ERROR_NONE { | ||||
| @@ -29,5 +29,19 @@ copy_file_sync :: proc( path_src, path_dst: string ) -> bool | ||||
| 		runtime.debug_trap() | ||||
| 		return false | ||||
| 	} | ||||
|     return true | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| is_file_locked :: proc( file_path: string ) -> b32 { | ||||
| 	// Try to open the file for read access without sharing. | ||||
| 	// If the file is locked, the call will fail. | ||||
| 	handle, err := os.open(file_path, os.O_RDONLY) | ||||
| 	if err != os.ERROR_NONE { | ||||
| 			// If the error indicates the file is in use, return true. | ||||
| 			return true | ||||
| 	} | ||||
|  | ||||
| 	// If the file opens successfully, close it and return false. | ||||
| 	os.close(handle) | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -16,15 +16,13 @@ import      "core:mem/virtual" | ||||
| import       "core:os" | ||||
| import       "core:runtime" | ||||
| import       "core:strings" | ||||
| import       "core:time" | ||||
| import rl    "vendor:raylib" | ||||
| import sectr "../." | ||||
|  | ||||
| RuntimeState :: struct { | ||||
| 	running : b32, | ||||
|  | ||||
|  | ||||
| 	memory : VMemChunk, | ||||
|  | ||||
| 	running   : b32, | ||||
| 	memory    : VMemChunk, | ||||
| 	sectr_api : sectr.ModuleAPI, | ||||
| } | ||||
|  | ||||
| @@ -39,8 +37,7 @@ VMemChunk :: struct { | ||||
|  | ||||
| setup_engine_memory :: proc () -> VMemChunk | ||||
| { | ||||
| 	memory : VMemChunk | ||||
| 	using memory | ||||
| 	memory : VMemChunk; using memory | ||||
|  | ||||
| 	arena_init :: mem.arena_init | ||||
| 	ptr_offset :: mem.ptr_offset | ||||
| @@ -58,24 +55,21 @@ setup_engine_memory :: proc () -> VMemChunk | ||||
| 	} | ||||
|  | ||||
| 	// For now I'm making persistent sections each 128 meg and transient sections w/e is left over / 2 (one for engine the other for the env) | ||||
| 	persistent_size :: Megabyte * 128 * 2 | ||||
| 	transient_size  :: (Gigabyte * 2 - persistent_size * 2) / 2 | ||||
|  | ||||
| 	persistent_size     :: Megabyte * 128 * 2 | ||||
| 	transient_size      :: (Gigabyte * 2 - persistent_size * 2) / 2 | ||||
| 	eng_persistent_size :: persistent_size / 4 | ||||
| 	eng_transient_size  :: transient_size  / 4 | ||||
|  | ||||
| 	env_persistent_size :: persistent_size - eng_persistent_size | ||||
| 	env_trans_temp_size :: (transient_size  - eng_transient_size) / 2 | ||||
|  | ||||
| 	block := memory.sarena.curr_block | ||||
|  | ||||
| 	// Try to get a slice for each segment | ||||
| 	eng_persistent_slice := slice_ptr( block.base,                                      eng_persistent_size) | ||||
| 	eng_persistent_slice := slice_ptr( block.base,                                       eng_persistent_size) | ||||
| 	eng_transient_slice  := slice_ptr( & eng_persistent_slice[ eng_persistent_size - 1], eng_transient_size) | ||||
| 	env_persistent_slice := slice_ptr( & eng_transient_slice [ eng_transient_size  - 1], env_persistent_size) | ||||
| 	env_transient_slice  := slice_ptr( & env_persistent_slice[ env_persistent_size - 1], env_trans_temp_size) | ||||
| 	env_temp_slice       := slice_ptr( & env_transient_slice [ env_trans_temp_size - 1], env_trans_temp_size) | ||||
|  | ||||
| 	arena_init( & eng_persistent, eng_persistent_slice ) | ||||
| 	arena_init( & eng_transient,  eng_transient_slice  ) | ||||
| 	arena_init( & env_persistent, env_persistent_slice ) | ||||
| @@ -88,7 +82,7 @@ load_sectr_api :: proc ( version_id : i32 ) -> sectr.ModuleAPI | ||||
| { | ||||
| 	loaded_module : sectr.ModuleAPI | ||||
|  | ||||
| 	load_time, | ||||
| 	write_time, | ||||
| 	   result := os.last_write_time_by_name("sectr.dll") | ||||
| 	if result != os.ERROR_NONE { | ||||
| 		fmt.    println("Could not resolve the last write time for sectr.dll") | ||||
| @@ -106,26 +100,53 @@ load_sectr_api :: proc ( version_id : i32 ) -> sectr.ModuleAPI | ||||
| 		return {} | ||||
| 	} | ||||
|  | ||||
| 	startup  := cast( type_of( sectr.startup        )) dynlib.symbol_address( lib, "startup" ) | ||||
| 	shutdown := cast( type_of( sectr.sectr_shutdown )) dynlib.symbol_address( lib, "sectr_shutdown" ) | ||||
| 	reload   := cast( type_of( sectr.reload         )) dynlib.symbol_address( lib, "reload" ) | ||||
| 	update   := cast( type_of( sectr.update         )) dynlib.symbol_address( lib, "update" ) | ||||
| 	render   := cast( type_of( sectr.render         )) dynlib.symbol_address( lib, "render" ) | ||||
|  | ||||
| 	missing_symbol : b32 = false | ||||
| 	if startup  == nil do fmt.println("Failed to load sectr.startup symbol") | ||||
| 	if shutdown == nil do fmt.println("Failed to load sectr.shutdown symbol") | ||||
| 	if reload   == nil do fmt.println("Failed to load sectr.reload symbol") | ||||
| 	if update   == nil do fmt.println("Failed to load sectr.update symbol") | ||||
| 	if render   == nil do fmt.println("Failed to load sectr.render symbol") | ||||
| 	if missing_symbol { | ||||
| 		runtime.debug_trap() | ||||
| 		return {} | ||||
| 	} | ||||
|  | ||||
| 	loaded_module = { | ||||
| 		lib         = lib, | ||||
| 		load_time   = load_time, | ||||
| 		write_time  = write_time, | ||||
| 		lib_version = version_id, | ||||
|  | ||||
| 		startup  = cast( type_of( sectr.startup        )) dynlib.symbol_address( lib, "startup" ), | ||||
| 		shutdown = cast( type_of( sectr.sectr_shutdown )) dynlib.symbol_address( lib, "sectr_shutdown" ), | ||||
| 		reload   = cast( type_of( sectr.reload         )) dynlib.symbol_address( lib, "reload" ), | ||||
| 		update   = cast( type_of( sectr.update         )) dynlib.symbol_address( lib, "update" ), | ||||
| 		render   = cast( type_of( sectr.render         )) dynlib.symbol_address( lib, "render" ) | ||||
| 		startup  = startup, | ||||
| 		shutdown = shutdown, | ||||
| 		reload   = reload, | ||||
| 		update   = update, | ||||
| 		render   = render | ||||
| 	} | ||||
| 	return loaded_module | ||||
| } | ||||
|  | ||||
| unload_sectr_api :: proc ( module : ^ sectr.ModuleAPI ) | ||||
| { | ||||
| 	lock_file := fmt.tprintf( "sectr_{0}_locked.dll", module.lib_version ) | ||||
| 	dynlib.unload_library( module.lib ) | ||||
| 	// os.remove( lock_file ) | ||||
| 	module^ = {} | ||||
| } | ||||
|  | ||||
| main :: proc() | ||||
| { | ||||
| 	fmt.println("Hellope!") | ||||
|  | ||||
| 	state : RuntimeState | ||||
| 	using state | ||||
|  | ||||
| 	// Basic Giant VMem Block | ||||
| 	memory : VMemChunk | ||||
| 	{ | ||||
| 		// By default odin uses a growing arena for the runtime context | ||||
| 		// We're going to make it static for the prototype and separate it from the 'project' memory. | ||||
| @@ -137,7 +158,6 @@ main :: proc() | ||||
| 	} | ||||
|  | ||||
| 	// Load the Enviornment API for the first-time | ||||
| 	sectr_api : sectr.ModuleAPI | ||||
| 	{ | ||||
| 		   sectr_api = load_sectr_api( 1 ) | ||||
| 		if sectr_api.lib_version == 0 { | ||||
| @@ -147,27 +167,40 @@ main :: proc() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	state : RuntimeState | ||||
| 	state.running            = true; | ||||
| 	state.memory             = memory | ||||
| 	state.sectr_api          = sectr_api | ||||
| 	running            = true; | ||||
| 	memory             = memory | ||||
| 	sectr_api          = sectr_api | ||||
| 	sectr_api.startup( & memory.env_persistent, & memory.env_transient, & memory.env_temp ) | ||||
|  | ||||
| 	state.sectr_api.startup( & memory.env_persistent, & memory.env_transient, & memory.env_temp ) | ||||
|  | ||||
| 	// TODO(Ed) : This should return a end status so that we know the reason the engine stopped. | ||||
| 	for ; state.running ; | ||||
| 	// TODO(Ed) : This should have an end status so that we know the reason the engine stopped. | ||||
| 	for ; running ; | ||||
| 	{ | ||||
| 		// Hot-Reload | ||||
| 		// TODO(ED) : Detect if currently loaded code is outdated. | ||||
| 		if write_time, result := os.last_write_time_by_name("sectr.dll"); | ||||
| 			result == os.ERROR_NONE && sectr_api.write_time != write_time | ||||
| 		{ | ||||
| 			// state.sectr_api.reload() | ||||
| 			version_id := sectr_api.lib_version + 1 | ||||
| 			unload_sectr_api( & sectr_api ) | ||||
|  | ||||
| 			// Wait for pdb to unlock (linker may still be writting) | ||||
| 			for ; sectr.is_file_locked( "sectr.pdb" ); { | ||||
| 			} | ||||
| 			time.sleep( time.Millisecond ) | ||||
|  | ||||
| 			sectr_api = load_sectr_api( version_id ) | ||||
| 			if sectr_api.lib_version == 0 { | ||||
| 				fmt.println("Failed to hot-reload the sectr module") | ||||
| 				runtime.debug_trap() | ||||
| 				os.exit(-1) | ||||
| 			} | ||||
| 			sectr_api.reload( & memory.env_persistent, & memory.env_transient, & memory.env_temp ) | ||||
| 		} | ||||
|  | ||||
| 		// Logic Update | ||||
| 		state.running = state.sectr_api.update() | ||||
| 		running = sectr_api.update() | ||||
| 		sectr_api.render() | ||||
|  | ||||
| 		// Rendering | ||||
| 		state.sectr_api.render() | ||||
| 		free_all( mem.arena_allocator( & memory.env_temp ) ) | ||||
| 		// free_all( mem.arena_allocator( & memory.env_transient ) ) | ||||
| 	} | ||||
|  | ||||
| 	// Determine how the run_cyle completed, if it failed due to an error, | ||||
| @@ -176,5 +209,6 @@ main :: proc() | ||||
| 		// TODO(Ed): Implement this. | ||||
| 	} | ||||
|  | ||||
| 	state.sectr_api.shutdown() | ||||
| 	sectr_api.shutdown() | ||||
| 	unload_sectr_api( & sectr_api ) | ||||
| } | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:unicode/utf8" | ||||
|  | ||||
| import    "core:unicode/utf8" | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| font_rec_mono_semicasual_reg : Font; | ||||
| default_font                 : Font | ||||
|  | ||||
| debug_text :: proc( content : string, x, y : f32, size : f32 = 16.0, color : rl.Color = rl.WHITE, font : rl.Font = default_font ) | ||||
| debug_text :: proc( content : string, x, y : f32, size : f32 = 16.0, color : rl.Color = rl.WHITE, font : rl.Font = {} ) | ||||
| { | ||||
| 	if len( content ) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	runes := utf8.string_to_runes( content, context.temp_allocator ) | ||||
|  | ||||
| 	font := font | ||||
| 	if ( font.chars == nil ) { | ||||
| 		font = ( cast( ^ State) memory.persistent ).default_font | ||||
| 	} | ||||
|  | ||||
| 	rl.DrawTextCodepoints( font, | ||||
| 		raw_data(runes), cast(i32) len(runes), | ||||
| 		position = rl.Vector2 { x, y }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user