diff --git a/code/api.odin b/code/api.odin index a902b74..7b0414d 100644 --- a/code/api.odin +++ b/code/api.odin @@ -3,7 +3,9 @@ package sectr import "core:dynlib" import "core:fmt" import "core:mem" +import "core:mem/virtual" import "core:os" +import "core:slice" import "core:strings" import rl "vendor:raylib" @@ -14,30 +16,60 @@ ModuleAPI :: struct { write_time : os.File_Time, lib_version : i32, - startup : type_of( startup ), - shutdown : type_of( sectr_shutdown), - reload : type_of( reload ), - update : type_of( update ), - render : type_of( render ) + startup : type_of( startup ), + shutdown : type_of( sectr_shutdown), + reload : type_of( reload ), + update : type_of( update ), + render : type_of( render ), + clean_temp : type_of( clean_temp ), } +memory_chunk_size :: 2 * Gigabyte +memory_persistent_size :: 128 * Megabyte +memory_trans_temp_size :: (memory_chunk_size - memory_persistent_size ) / 2 + Memory :: struct { - persistent : ^ mem.Arena, - transient : ^ mem.Arena, - temp : ^ mem.Arena + live : ^ virtual.Arena, + snapshot : ^ virtual.Arena, + persistent : ^ TrackedAllocator, + transient : ^ TrackedAllocator, + temp : ^ TrackedAllocator } memory : Memory @export -startup :: proc( persistent, transient, temp : ^ mem.Arena ) +startup :: proc( live_mem, snapshot_mem : ^ virtual.Arena ) { - memory.persistent = persistent - state := cast(^State) memory.persistent; using state + // Setup memory for the first time + { + Arena :: mem.Arena + Tracking_Allocator :: mem.Tracking_Allocator + arena_allocator :: mem.arena_allocator + arena_init :: mem.arena_init + slice_ptr :: mem.slice_ptr - // Anything allocated by default is considered transient. - // context.allocator = mem.arena_allocator( transient ) - // context.temp_allocator = mem.arena_allocator( temp ) + arena_size :: size_of( mem.Arena) + internals_size :: 4 * Megabyte + + using memory; + block := live_mem.curr_block + + 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 ) + + // We assign the beginning of the block to be the host's persistent memory's arena. + // Then we offset past the arena and determine its slice to be the amount left after for the size of host's persistent. + persistent = tracked_allocator_init_vmem( persistent_slice, internals_size ) + transient = tracked_allocator_init_vmem( transient_slice, internals_size ) + temp = tracked_allocator_init_vmem( temp_slice , internals_size ) + + // context.allocator = tracked_allocator( transient ) + // context.temp_allocator = tracked_allocator( temp ) + } + state := new( State, tracked_allocator( memory.persistent ) ) + using state // Rough setup of window with rl stuff screen_width = 1280 @@ -70,25 +102,35 @@ sectr_shutdown :: proc() if memory.persistent == nil { return } - state := cast( ^ State ) memory.persistent + state := get_state() rl.UnloadFont( state.font_rec_mono_semicasual_reg ) rl.CloseWindow() } @export -reload :: proc( persistent, transient, temp : ^ mem.Arena ) +reload :: proc( live_mem, snapshot_mem : ^ virtual.Arena ) { - memory.persistent = persistent - memory.transient = transient - memory.temp = temp - context.allocator = mem.arena_allocator( transient ) - context.temp_allocator = mem.arena_allocator( temp ) + Arena :: mem.Arena + Tracking_Allocator :: mem.Tracking_Allocator + arena_allocator :: mem.arena_allocator + slice_ptr :: mem.slice_ptr + + using memory; + block := live_mem.curr_block + + 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 ) + + persistent = cast( ^TrackedAllocator ) & persistent_slice[0] + transient = cast( ^TrackedAllocator ) & transient_slice[0] + temp = cast( ^TrackedAllocator ) & temp_slice[0] } @export update :: proc() -> b32 { - state := cast( ^ State ) memory.persistent + state := get_state(); using state should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() return should_shutdown @@ -97,7 +139,7 @@ update :: proc() -> b32 @export render :: proc() { - state := cast( ^ State ) memory.persistent; using state + state := get_state(); using state rl.BeginDrawing() rl.ClearBackground( Color_BG ) @@ -111,7 +153,7 @@ render :: proc() { @static draw_text_scratch : [Kilobyte * 64]u8 - state := cast( ^ State ) memory.persistent; using state + state := get_state(); using state if ( draw_debug_text_y > 800 ) { draw_debug_text_y = 50 } @@ -122,10 +164,21 @@ render :: proc() draw_debug_text_y += 16 } - draw_text( "Monitor : %v", rl.GetMonitorName(0) ) + // draw_text( "Hot-Reload Count : %v", -1 ) + draw_text( "Screen Width : %v", rl.GetScreenWidth() ) draw_text( "Screen Height: %v", rl.GetScreenHeight() ) - // draw_text( "HOT RELOAD BITCHES" ) draw_debug_text_y = 50 } + +@export +clean_temp :: proc() +{ + mem.tracking_allocator_clear( & memory.temp.tracker ) +} + +get_state :: proc() -> (^ State) +{ + return cast(^ State) raw_data( memory.persistent.backing.data ) +} diff --git a/code/host/host.odin b/code/host/host.odin index b82076f..3a533a6 100644 --- a/code/host/host.odin +++ b/code/host/host.odin @@ -20,7 +20,17 @@ import "core:time" import rl "vendor:raylib" import sectr "../." +TrackedAllocator :: sectr.TrackedAllocator +tracked_allocator :: sectr.tracked_allocator +tracked_allocator_init :: sectr.tracked_allocator_init + path_snapshot :: "VMemChunk_1.snapshot" +when ODIN_OS == runtime.Odin_OS_Type.Windows +{ + path_sectr_module :: "sectr.dll" + path_sectr_live_module :: "sectr_live.dll" + path_sectr_debug_symbols :: "sectr.pdb" +} RuntimeState :: struct { running : b32, @@ -29,77 +39,46 @@ RuntimeState :: struct { } VMemChunk :: struct { - sarena : virtual.Arena, - host_persistent : ^ mem.Arena, - host_transient : ^ mem.Arena, - sectr_persistent : ^ mem.Arena, - sectr_transient : ^ mem.Arena, - sectr_temp : ^ mem.Arena, - - // snapshot : + og_allocator : mem.Allocator, + og_temp_allocator : mem.Allocator, + host_persistent : TrackedAllocator, + host_transient : TrackedAllocator, + sectr_live : virtual.Arena, + sectr_snapshot : virtual.Arena } -setup_engine_memory :: proc () -> VMemChunk +setup_memory :: proc () -> VMemChunk { + Arena :: mem.Arena + Tracking_Allocator :: mem.Tracking_Allocator memory : VMemChunk; using memory - Arena :: mem.Arena - arena_init :: mem.arena_init - ptr_offset :: mem.ptr_offset - slice_ptr :: mem.slice_ptr + host_persistent_size :: 32 * Megabyte + host_transient_size :: 96 * Megabyte + internals_size :: 4 * Megabyte - chunk_size :: 2 * Gigabyte + host_persistent = tracked_allocator_init( host_persistent_size, internals_size ) + host_transient = tracked_allocator_init( host_transient_size, internals_size ) // Setup the static arena for the entire application - if result := virtual.arena_init_static( & sarena, chunk_size, chunk_size ); - result != runtime.Allocator_Error.None + if result := virtual.arena_init_static( & sectr_live, sectr.memory_chunk_size, sectr.memory_chunk_size ); + result != runtime.Allocator_Error.None { // TODO(Ed) : Setup a proper logging interface - fmt. printf( "Failed to allocate memory for the engine" ) + fmt. printf( "Failed to allocate memory for the sectr module" ) runtime.debug_trap() os. exit( -1 ) // TODO(Ed) : Figure out the error code enums.. } - arena_size :: size_of( Arena) - - persistent_size :: Megabyte * 128 * 2 - transient_size :: (chunk_size - persistent_size * 2) / 2 - host_persistent_size :: persistent_size / 4 - arena_size - host_transient_size :: transient_size / 4 - arena_size - sectr_persistent_size :: persistent_size - host_persistent_size - arena_size - sectr_trans_temp_size :: (transient_size - host_transient_size) / 2 - arena_size - - block := memory.sarena.curr_block - - // We assign the beginning of the block to be the host's persistent memory's arena. - // Then we offset past the arena and determine its slice to be the amount left after for the size of host's persistent. - host_persistent = cast( ^ Arena ) block.base - host_persistent_slice := slice_ptr( ptr_offset( block.base, arena_size), host_persistent_size) - arena_init( host_persistent, host_persistent_slice ) - - // Initialize a sub-section of our virtual memory as a sub-arena - sub_arena_init :: proc( address : ^ byte, size : int ) -> ( ^ Arena) { - sub_arena := cast( ^ Arena ) address - mem_slice := slice_ptr( ptr_offset( address, arena_size), size ) - arena_init( sub_arena, mem_slice ) - return sub_arena - } - - // Helper to get the the beginning of memory after a slice - next :: proc( slice : []byte ) -> ( ^ byte) { - return ptr_offset( & slice[0], len(slice) ) - } - - host_transient = sub_arena_init( next( host_persistent.data), host_transient_size) - sectr_persistent = sub_arena_init( next( host_transient.data), sectr_persistent_size) - sectr_transient = sub_arena_init( next( sectr_persistent.data), sectr_trans_temp_size) - sectr_temp = sub_arena_init( next( sectr_transient.data), sectr_trans_temp_size) + // Reassign default allocators for host + memory.og_allocator = context.allocator + memory.og_temp_allocator = context.temp_allocator + context.allocator = tracked_allocator( & memory.host_persistent ) + context.temp_allocator = tracked_allocator( & memory.host_transient ) return memory; } -setup_snapshot_memory :: proc () - load_sectr_api :: proc ( version_id : i32 ) -> sectr.ModuleAPI { loaded_module : sectr.ModuleAPI @@ -112,28 +91,31 @@ load_sectr_api :: proc ( version_id : i32 ) -> sectr.ModuleAPI return {} } - lock_file := fmt.tprintf( "sectr_{0}_locked.dll", version_id ) - sectr.copy_file_sync( "sectr.dll", lock_file ) + live_file := path_sectr_live_module + sectr.copy_file_sync( path_sectr_module, live_file ) - lib, load_result := dynlib.load_library( lock_file ) + lib, load_result := dynlib.load_library( live_file ) if ! load_result { + // TODO(Ed) : Setup a proper logging interface fmt. println( "Failed to load the sectr module." ) runtime.debug_trap() 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" ) + 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" ) + clean_temp := cast( type_of( sectr.clean_temp )) dynlib.symbol_address( lib, "clean_temp" ) 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 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 clean_temp == nil do fmt.println("Failed to load sector.clean_temp symbol") if missing_symbol { runtime.debug_trap() return {} @@ -144,23 +126,46 @@ load_sectr_api :: proc ( version_id : i32 ) -> sectr.ModuleAPI write_time = write_time, lib_version = version_id, - startup = startup, - shutdown = shutdown, - reload = reload, - update = update, - render = render, + startup = startup, + shutdown = shutdown, + reload = reload, + update = update, + render = render, + clean_temp = clean_temp, } 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 ) + os.remove( path_sectr_live_module ) module^ = {} } +sync_sectr_api :: proc ( sectr_api : ^ sectr.ModuleAPI, memory : ^ VMemChunk ) +{ + if write_time, result := os.last_write_time_by_name( path_sectr_module ); + result == os.ERROR_NONE && sectr_api.write_time != write_time + { + 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( path_sectr_debug_symbols ) && sectr.is_file_locked( path_sectr_live_module ); {} + 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) + // TODO(Ed) : Figure out the error code enums.. + } + sectr_api.reload( & memory.sectr_live, & memory.sectr_snapshot ) + } +} + main :: proc() { fmt.println("Hellope!") @@ -174,55 +179,35 @@ main :: proc() // We're going to make it static for the prototype and separate it from the 'project' memory. // Then shove the context allocator for the engine to it. // The project's context will use its own subsection arena allocator. - memory = setup_engine_memory() - context.allocator = mem.arena_allocator( memory.host_persistent ) - context.temp_allocator = mem.arena_allocator( memory.host_transient ) + memory = setup_memory() } // Load the Enviornment API for the first-time { sectr_api = load_sectr_api( 1 ) if sectr_api.lib_version == 0 { + // TODO(Ed) : Setup a proper logging interface fmt. println( "Failed to initially load the sectr module" ) runtime.debug_trap() os. exit( -1 ) + // TODO(Ed) : Figure out the error code enums.. } } running = true; memory = memory sectr_api = sectr_api - sectr_api.startup( memory.sectr_persistent, memory.sectr_transient, memory.sectr_temp ) + sectr_api.startup( & memory.sectr_live, & memory.sectr_snapshot ) // TODO(Ed) : This should have an end status so that we know the reason the engine stopped. for ; running ; { // Hot-Reload - if write_time, result := os.last_write_time_by_name("sectr.dll"); - result == os.ERROR_NONE && sectr_api.write_time != write_time - { - 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.sectr_persistent, memory.sectr_transient, memory.sectr_temp ) - } + sync_sectr_api( & sectr_api, & memory ) running = sectr_api.update() - sectr_api.render() - - free_all( mem.arena_allocator( memory.sectr_temp ) ) - // free_all( mem.arena_allocator( & memory.env_transient ) ) + sectr_api.render() + sectr_api.clean_temp() } // Determine how the run_cyle completed, if it failed due to an error, diff --git a/code/memory.odin b/code/memory.odin index 8b66b50..7f59714 100644 --- a/code/memory.odin +++ b/code/memory.odin @@ -4,6 +4,7 @@ import "core:fmt" import "core:mem" import "core:mem/virtual" import "core:runtime" +import "core:os" Byte :: 1 Kilobyte :: 1024 * Byte @@ -14,8 +15,112 @@ Petabyte :: 1024 * Terabyte Exabyte :: 1024 * Petabyte kilobytes :: proc ( kb : $ integer_type ) -> integer_type { - return kb * 1024 + return kb * Kilobyte } -megabytes :: proc ( kb : $ integer_type ) -> integer_type { - return kb * 1024 * 1024 +megabytes :: proc ( mb : $ integer_type ) -> integer_type { + return mb * Megabyte +} +gigabyte :: proc ( gb : $ integer_type ) -> integer_type { + return gb * Gigabyte +} +terabyte :: proc ( tb : $ integer_type ) -> integer_type { + return tb * Terabyte +} + +// Initialize a sub-section of our virtual memory as a sub-arena +sub_arena_init :: proc( address : ^ byte, size : int ) -> ( ^ mem.Arena) { + Arena :: mem.Arena + + arena_size :: size_of( Arena) + sub_arena := cast( ^ Arena ) address + mem_slice := mem.slice_ptr( mem.ptr_offset( address, arena_size), size ) + mem.arena_init( sub_arena, mem_slice ) + return sub_arena +} + +// Helper to get the the beginning of memory after a slice +memory_after :: proc( slice : []byte ) -> ( ^ byte) { + return mem.ptr_offset( & slice[0], len(slice) ) +} + +// Since this is a prototype, all memory is always tracked. No arena is is interfaced directly. +TrackedAllocator :: struct { + backing : mem.Arena, + internals : mem.Arena, + tracker : mem.Tracking_Allocator, +} + +tracked_allocator :: proc ( self : ^ TrackedAllocator ) -> mem.Allocator { + return mem.tracking_allocator( & self.tracker ) +} + +tracked_allocator_init :: proc( size, internals_size : int ) -> TrackedAllocator +{ + result : TrackedAllocator + + Arena :: mem.Arena + Tracking_Allocator :: mem.Tracking_Allocator + arena_allocator :: mem.arena_allocator + arena_init :: mem.arena_init + slice_ptr :: mem.slice_ptr + + arena_size :: size_of( Arena) + backing_size := size + arena_size + internals_size := internals_size + arena_size + raw_size := backing_size + internals_size + + raw_mem, raw_mem_code := mem.alloc( raw_size ) + if ( raw_mem_code != mem.Allocator_Error.None ) + { + // TODO(Ed) : Setup a proper logging interface + fmt. printf( "Failed to allocate memory for the TrackingAllocator" ) + runtime.debug_trap() + os. exit( -1 ) + // TODO(Ed) : Figure out the error code enums.. + } + arena_init( & result.backing, slice_ptr( cast( ^ byte) raw_mem, backing_size ) ) + arena_init( & result.internals, slice_ptr( memory_after( result.backing.data), internals_size ) ) + + backing_allocator := arena_allocator( & result.backing ) + internals_allocator := arena_allocator( & result.internals ) + mem.tracking_allocator_init( & result.tracker, backing_allocator, internals_allocator ) + return result +} + +tracked_allocator_init_vmem :: proc( vmem : [] byte, internals_size : int ) -> ^ TrackedAllocator +{ + Arena :: mem.Arena + Tracking_Allocator :: mem.Tracking_Allocator + arena_allocator :: mem.arena_allocator + arena_init :: mem.arena_init + tracking_alloator_init :: mem.tracking_allocator_init + ptr_offset :: mem.ptr_offset + slice_ptr :: mem.slice_ptr + + arena_size :: size_of( Arena) + tracking_allocator_size :: size_of( Tracking_Allocator ) + backing_size := len(vmem) - internals_size + raw_size := backing_size + internals_size + + if backing_size < 0 || len(vmem) < raw_size + { + // TODO(Ed) : Setup a proper logging interface + fmt. printf( "Provided virtual memory slice is not large enough to hold the TrackedAllocator" ) + runtime.debug_trap() + os. exit( -1 ) + // TODO(Ed) : Figure out the error code enums.. + } + + result := cast( ^ TrackedAllocator) & vmem[0] + result_slice := slice_ptr( & vmem[0], tracking_allocator_size ) + + backing_slice := slice_ptr( memory_after( result_slice ), backing_size ) + internals_slice := slice_ptr( memory_after( backing_slice), internals_size ) + backing := & result.backing + internals := & result.internals + arena_init( backing, backing_slice ) + arena_init( internals, internals_slice ) + + tracking_alloator_init( & result.tracker, arena_allocator( backing ), arena_allocator( internals ) ) + return result } diff --git a/code/text.odin b/code/text.odin index d35635e..e95239c 100644 --- a/code/text.odin +++ b/code/text.odin @@ -12,7 +12,7 @@ debug_text :: proc( content : string, x, y : f32, size : f32 = 16.0, color : rl. font := font if ( font.chars == nil ) { - font = ( cast( ^ State) memory.persistent ).default_font + font = get_state().default_font } rl.DrawTextCodepoints( font,