SectrPrototype/code/host/host.odin

386 lines
12 KiB
Odin
Raw Normal View History

2024-03-05 07:15:44 -08:00
/* Sectr Host Executable
Manages the client module (sectr) application & loads its required memory to operate.
Reserves the virtual memory spaces for the following:
* Persistent
* Frame
* Transient
* FilesBuffer
Currently the prototype has hot-reload always enabled, eventually there will be conditional compliation to omit if when desired.
*/
package sectr_host
2024-01-21 20:38:02 -08:00
//region Grime & Dependencies
import "base:runtime"
Byte :: runtime.Byte
Kilobyte :: runtime.Kilobyte
Megabyte :: runtime.Megabyte
Gigabyte :: runtime.Gigabyte
Terabyte :: runtime.Terabyte
Petabyte :: runtime.Petabyte
Exabyte :: runtime.Exabyte
import "core:dynlib"
os_lib_load :: dynlib.load_library
os_lib_unload :: dynlib.unload_library
os_lib_get_proc :: dynlib.symbol_address
import "core:io"
import fmt_io "core:fmt"
str_fmt :: fmt_io.printf
2024-03-08 20:20:49 -08:00
str_fmt_alloc :: fmt_io.aprintf
str_fmt_tmp :: fmt_io.tprintf
2024-03-13 21:00:44 -07:00
str_fmt_buffer :: fmt_io.bprintf
str_fmt_builder :: fmt_io.sbprintf
import "core:log"
import "core:mem"
Allocator :: mem.Allocator
AllocatorError :: mem.Allocator_Error
2024-03-13 21:00:44 -07:00
Arena :: mem.Arena
arena_allocator :: mem.arena_allocator
import "core:mem/virtual"
MapFileError :: virtual.Map_File_Error
MapFileFlag :: virtual.Map_File_Flag
MapFileFlags :: virtual.Map_File_Flags
import "core:os"
FileFlag_Create :: os.O_CREATE
FileFlag_ReadWrite :: os.O_RDWR
file_open :: os.open
file_close :: os.close
file_rename :: os.rename
file_remove :: os.remove
file_resize :: os.ftruncate
file_status_via_handle :: os.fstat
file_status_via_path :: os.stat
import "core:strings"
builder_to_string :: strings.to_string
str_clone :: strings.clone
str_builder_from_bytes :: strings.builder_from_bytes
import "core:time"
Millisecond :: time.Millisecond
Second :: time.Second
Duration :: time.Duration
duration_seconds :: time.duration_seconds
thread_sleep :: time.sleep
import "core:prof/spall"
2024-01-21 20:38:02 -08:00
import rl "vendor:raylib"
import "codebase:grime"
file_copy_sync :: grime.file_copy_sync
file_is_locked :: grime.file_is_locked
varena_init :: grime.varena_init
import "codebase:sectr"
VArena :: sectr.VArena
fatal :: sectr.fatal
Logger :: sectr.Logger
logger_init :: sectr.logger_init
LogLevel :: sectr.LogLevel
log :: sectr.log
SpallProfiler :: sectr.SpallProfiler
to_odin_logger :: sectr.to_odin_logger
verify :: sectr.verify
file_status :: proc {
file_status_via_handle,
file_status_via_path,
}
2024-01-21 20:38:02 -08:00
to_str :: proc {
builder_to_string,
}
//endregion Grime & Dependencies
Path_Snapshot :: "VMemChunk_1.snapshot"
Path_Logs :: "../logs"
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"
}
2024-01-25 07:49:57 -08:00
// TODO(Ed): Disable the default allocators for the host, we'll be handling it instead.
2024-01-21 20:38:02 -08:00
RuntimeState :: struct {
2024-03-13 21:00:44 -07:00
persistent : Arena,
transient : Arena,
running : b32,
client_memory : ClientMemory,
sectr_api : sectr.ModuleAPI,
2024-01-21 20:38:02 -08:00
}
ClientMemory :: struct {
persistent : VArena,
frame : VArena,
transient : VArena,
files_buffer : VArena,
2024-01-21 20:38:02 -08:00
}
setup_memory :: proc( profiler : ^SpallProfiler ) -> ClientMemory
2024-01-21 20:38:02 -08:00
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
memory : ClientMemory; using memory
2024-01-25 07:49:57 -08:00
2024-01-21 20:38:02 -08:00
// Setup the static arena for the entire application
{
alloc_error : AllocatorError
2024-06-20 11:39:50 -07:00
persistent, alloc_error = varena_init(
sectr.Memory_Base_Address_Persistent,
sectr.Memory_Reserve_Persistent,
sectr.Memory_Commit_Initial_Persistent,
growth_policy = nil,
allow_any_resize = true,
2024-06-20 12:36:47 -07:00
dbg_name = "persistent",
enable_mem_tracking = false )
verify( alloc_error == .None, "Failed to allocate persistent virtual arena for the sectr module")
2024-06-20 11:39:50 -07:00
frame, alloc_error = varena_init(
sectr.Memory_Base_Address_Frame,
sectr.Memory_Reserve_Frame,
sectr.Memory_Commit_Initial_Frame,
growth_policy = nil,
allow_any_resize = true,
dbg_name = "frame" )
verify( alloc_error == .None, "Failed to allocate frame virtual arena for the sectr module")
2024-06-20 11:39:50 -07:00
transient, alloc_error = varena_init(
sectr.Memory_Base_Address_Transient,
sectr.Memory_Reserve_Transient,
sectr.Memory_Commit_Initial_Transient,
growth_policy = nil,
allow_any_resize = true,
dbg_name = "transient" )
verify( alloc_error == .None, "Failed to allocate transient virtual arena for the sectr module")
2024-06-20 11:39:50 -07:00
files_buffer, alloc_error = varena_init(
sectr.Memory_Base_Address_Files_Buffer,
sectr.Memory_Reserve_FilesBuffer,
sectr.Memory_Commit_Initial_Filebuffer,
growth_policy = nil,
allow_any_resize = true,
dbg_name = "files_buffer" )
verify( alloc_error == .None, "Failed to allocate files buffer virtual arena for the sectr module")
}
// Setup memory mapped io for snapshots
// TODO(Ed) : We cannot do this with our growing arenas. Instead we need to map on demand for saving and loading
when false
{
snapshot_file, open_error := file_open( Path_Snapshot, FileFlag_ReadWrite | FileFlag_Create )
verify( open_error == os.ERROR_NONE, "Failed to open snapshot file for the sectr module" )
file_info, stat_code := file_status( snapshot_file )
{
if file_info.size != sectr.Memory_Chunk_Size {
file_resize( snapshot_file, sectr.Memory_Chunk_Size )
}
}
2024-06-20 11:39:50 -07:00
map_error : MapFileError
map_flags : MapFileFlags = { MapFileFlag.Read, MapFileFlag.Write }
sectr_snapshot, map_error = virtual.map_file_from_file_descriptor( uintptr(snapshot_file), map_flags )
verify( map_error == MapFileError.None, "Failed to allocate snapshot memory for the sectr module" )
file_close(snapshot_file)
2024-01-21 20:38:02 -08:00
}
log("Memory setup")
2024-01-21 20:38:02 -08:00
return memory;
}
2024-03-05 07:15:44 -08:00
load_sectr_api :: proc( version_id : i32 ) -> (loaded_module : sectr.ModuleAPI)
2024-01-21 20:38:02 -08:00
{
write_time, result := os.last_write_time_by_name("sectr.dll")
2024-01-21 20:38:02 -08:00
if result != os.ERROR_NONE {
log( "Could not resolve the last write time for sectr.dll", LogLevel.Warning )
2024-01-21 20:38:02 -08:00
runtime.debug_trap()
2024-03-05 07:15:44 -08:00
return
2024-01-21 20:38:02 -08:00
}
thread_sleep( Millisecond * 100 )
live_file := Path_Sectr_Live_Module
file_copy_sync( Path_Sectr_Module, live_file, allocator = context.temp_allocator )
2024-01-21 20:38:02 -08:00
lib, load_result := os_lib_load( live_file )
2024-01-21 20:38:02 -08:00
if ! load_result {
log( "Failed to load the sectr module.", LogLevel.Warning )
2024-01-21 20:38:02 -08:00
runtime.debug_trap()
2024-03-05 07:15:44 -08:00
return
2024-01-21 20:38:02 -08:00
}
startup := cast( type_of( sectr.startup )) os_lib_get_proc( lib, "startup" )
shutdown := cast( type_of( sectr.sectr_shutdown )) os_lib_get_proc( lib, "sectr_shutdown" )
reload := cast( type_of( sectr.hot_reload )) os_lib_get_proc( lib, "hot_reload" )
tick := cast( type_of( sectr.tick )) os_lib_get_proc( lib, "tick" )
clean_frame := cast( type_of( sectr.clean_frame )) os_lib_get_proc( lib, "clean_frame" )
2024-01-22 00:47:53 -08:00
missing_symbol : b32 = false
if startup == nil do log("Failed to load sectr.startup symbol", LogLevel.Warning )
if shutdown == nil do log("Failed to load sectr.shutdown symbol", LogLevel.Warning )
if reload == nil do log("Failed to load sectr.reload symbol", LogLevel.Warning )
if tick == nil do log("Failed to load sectr.tick symbol", LogLevel.Warning )
if clean_frame == nil do log("Failed to load sector.clean_frame symbol", LogLevel.Warning )
2024-01-22 00:47:53 -08:00
if missing_symbol {
runtime.debug_trap()
2024-03-05 07:15:44 -08:00
return
2024-01-22 00:47:53 -08:00
}
log("Loaded sectr API")
2024-01-21 20:38:02 -08:00
loaded_module = {
lib = lib,
2024-01-22 00:47:53 -08:00
write_time = write_time,
2024-01-21 20:38:02 -08:00
lib_version = version_id,
startup = startup,
shutdown = shutdown,
reload = reload,
tick = tick,
clean_frame = clean_frame,
2024-01-21 20:38:02 -08:00
}
2024-03-05 07:15:44 -08:00
return
2024-01-21 20:38:02 -08:00
}
unload_sectr_api :: proc( module : ^ sectr.ModuleAPI )
2024-01-22 00:47:53 -08:00
{
os_lib_unload( module.lib )
file_remove( Path_Sectr_Live_Module )
2024-01-22 00:47:53 -08:00
module^ = {}
log("Unloaded sectr API")
2024-01-22 00:47:53 -08:00
}
sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, logger : ^Logger, profiler : ^SpallProfiler )
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
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 ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {}
thread_sleep( Millisecond * 100 )
(sectr_api ^) = load_sectr_api( version_id )
verify( sectr_api.lib_version != 0, "Failed to hot-reload the sectr module" )
sectr_api.reload(
profiler,
& memory.persistent,
& memory.frame,
& memory.transient,
& memory.files_buffer,
logger )
}
}
2024-03-13 21:00:44 -07:00
fmt_backing : [16 * Kilobyte] u8
2024-06-20 11:39:50 -07:00
persistent_backing : [2 * Megabyte] byte
transient_backing : [32 * Megabyte] byte
2024-03-13 21:00:44 -07:00
2024-01-21 20:38:02 -08:00
main :: proc()
{
2024-01-22 00:47:53 -08:00
state : RuntimeState
using state
2024-03-13 21:00:44 -07:00
mem.arena_init( & state.persistent, persistent_backing[:] )
mem.arena_init( & state.transient, transient_backing[:] )
context.allocator = arena_allocator( & state.persistent)
context.temp_allocator = arena_allocator( & state.transient)
// Setup profiling
profiler : SpallProfiler
{
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
profiler.ctx = spall.context_create("sectr.spall")
profiler.buffer = spall.buffer_create(buffer_backing)
}
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
// Generating the logger's name, it will be used when the app is shutting down.
path_logger_finalized : string
{
startup_time := time.now()
year, month, day := time.date( startup_time)
hour, min, sec := time.clock_from_time( startup_time)
if ! os.is_dir( Path_Logs ) {
os.make_directory( Path_Logs )
}
2024-03-13 21:00:44 -07:00
timestamp := str_fmt_buffer( fmt_backing[:], "%04d-%02d-%02d_%02d-%02d-%02d", year, month, day, hour, min, sec)
path_logger_finalized = str_fmt_buffer( fmt_backing[:], "%s/sectr_%v.log", Path_Logs, timestamp)
}
logger : sectr.Logger
2024-03-13 21:00:44 -07:00
logger_init( & logger, "Sectr Host", str_fmt_buffer( fmt_backing[:], "%s/sectr.log", Path_Logs ) )
context.logger = to_odin_logger( & logger )
{
// Log System Context
2024-03-13 21:00:44 -07:00
backing_builder : [1 * Kilobyte] u8
builder := str_builder_from_bytes( backing_builder[:] )
str_fmt_builder( & builder, "Core Count: %v, ", os.processor_core_count() )
str_fmt_builder( & builder, "Page Size: %v", os.get_page_size() )
log( to_str(builder) )
}
memory := setup_memory( & profiler )
2024-01-21 20:38:02 -08:00
// Load the Enviornment API for the first-time
{
sectr_api = load_sectr_api( 1 )
verify( sectr_api.lib_version != 0, "Failed to initially load the sectr module" )
2024-01-21 20:38:02 -08:00
}
2024-03-13 21:00:44 -07:00
// free_all( context.temp_allocator )
running = true;
sectr_api = sectr_api
sectr_api.startup(
& profiler,
& memory.persistent,
& memory.frame,
& memory.transient,
& memory.files_buffer,
& logger )
2024-01-21 20:38:02 -08:00
delta_ns : Duration
host_tick := time.tick_now()
2024-01-22 00:47:53 -08:00
// TODO(Ed) : This should have an end status so that we know the reason the engine stopped.
for ; running ;
2024-01-21 20:38:02 -08:00
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, "Host Tick" )
// Hot-Reload
sync_sectr_api( & sectr_api, & memory, & logger, & profiler )
running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns )
sectr_api.clean_frame()
delta_ns = time.tick_lap_time( & host_tick )
host_tick = time.tick_now()
2024-03-13 21:00:44 -07:00
free_all( arena_allocator( & state.transient))
2024-01-21 20:38:02 -08:00
}
// Determine how the run_cyle completed, if it failed due to an error,
// fallback the env to a failsafe state and reload the run_cycle.
{
// TODO(Ed): Implement this.
}
2024-01-22 00:47:53 -08:00
sectr_api.shutdown()
unload_sectr_api( & sectr_api )
spall.buffer_destroy( & profiler.ctx, & profiler.buffer )
spall.context_destroy( & profiler.ctx )
log("Succesfuly closed")
file_close( logger.file )
2024-03-13 21:00:44 -07:00
file_rename( str_fmt_buffer( fmt_backing[:], "%s/sectr.log", Path_Logs), path_logger_finalized )
2024-01-21 20:38:02 -08:00
}