Hot reload works

This commit is contained in:
Edward R. Gonzalez 2024-01-22 03:47:53 -05:00
parent 77a48d7104
commit 761794f594
6 changed files with 147 additions and 80 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 )
}

View File

@ -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 },

View File

@ -57,22 +57,35 @@ push-location $path_root
}
function build-prototype
{
$host_process_active = Get-Process | Where-Object {$_.Name -like 'sectr_host*'}
push-location $path_code
$project_name = 'sectr'
$executable = join-path $path_build ($project_name + '_host.exe')
$pdb = join-path $path_build ($project_name + '_host.pdb')
$build_args = @()
$build_args += $flag_build
$build_args += './host'
$build_args += $flag_output_path + $executable
$build_args += $flag_optimize_none
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'
if ( -not($host_process_active)) {
$build_args = @()
$build_args += $flag_build
$build_args += './host'
$build_args += $flag_output_path + $executable
$build_args += $flag_optimize_none
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'
& odin $build_args
& odin $build_args
$third_party_dlls = Get-ChildItem -Path $path_thirdparty -Filter '*.dll'
foreach ($dll in $third_party_dlls) {
$destination = join-path $path_build $dll.Name
Copy-Item $dll.FullName -Destination $destination -Force
}
}
else {
write-host 'Skipping sectr_host build, process is active'
}
$module_dll = join-path $path_build ( $project_name + '.dll' )
$pdb = join-path $path_build ( $project_name + '.pdb' )
@ -88,12 +101,6 @@ push-location $path_root
& odin $build_args
$third_party_dlls = Get-ChildItem -Path $path_thirdparty -Filter '*.dll'
foreach ($dll in $third_party_dlls) {
$destination = join-path $path_build $dll.Name
Copy-Item $dll.FullName -Destination $destination -Force
}
Pop-Location
}
build-prototype