Hot reload works
This commit is contained in:
parent
77a48d7104
commit
761794f594
@ -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 },
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user