hot reload works with tick lanes and job worker loops!
This commit is contained in:
@@ -7,24 +7,24 @@ This is just a snippet file, do not use directly.
|
||||
*/
|
||||
|
||||
set_profiler_module_context :: #force_inline proc "contextless" (profiler : ^Spall_Context) {
|
||||
sync_store(& static_memory.spall_context, profiler, .Release)
|
||||
sync_store(& grime_memory.spall_context, profiler, .Release)
|
||||
}
|
||||
|
||||
set_profiler_thread_buffer :: #force_inline proc "contextless" (buffer: ^Spall_Buffer) {
|
||||
sync_store(& thread_memory.spall_buffer, buffer, .Release)
|
||||
sync_store(& grime_thread.spall_buffer, buffer, .Release)
|
||||
}
|
||||
|
||||
DISABLE_PROFILING :: true
|
||||
|
||||
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
|
||||
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
spall._buffer_begin( static_memory.spall_context, thread_memory.spall_buffer, name, "", loc )
|
||||
spall._buffer_begin( grime_memory.spall_context, grime_thread.spall_buffer, name, "", loc )
|
||||
}
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
spall._buffer_begin( static_memory.spall_context, thread_memory.spall_buffer, name, "", loc )
|
||||
spall._buffer_begin( grime_memory.spall_context, grime_thread.spall_buffer, name, "", loc )
|
||||
}
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_end :: #force_inline proc "contextless" () {
|
||||
spall._buffer_end( static_memory.spall_context, thread_memory.spall_buffer)
|
||||
spall._buffer_end( grime_memory.spall_context, grime_thread.spall_buffer)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package grime
|
||||
|
||||
//region STATIC MEMORY
|
||||
static_memory: StaticMemory
|
||||
@thread_local thread_memory: ThreadMemory
|
||||
grime_memory: StaticMemory
|
||||
@thread_local grime_thread: ThreadMemory
|
||||
//endregion STATIC MEMORY
|
||||
|
||||
StaticMemory :: struct {
|
||||
|
||||
@@ -19,21 +19,23 @@ load_client_api :: proc(version_id: int) -> (loaded_module: Client_API) {
|
||||
//TODO(Ed): Lets try to minimize this...
|
||||
thread_sleep( Millisecond * 100 )
|
||||
// Get the live dll loaded up
|
||||
live_file := Path_Sectr_Live_Module
|
||||
file_copy_sync( Path_Sectr_Module, live_file, allocator = context.temp_allocator )
|
||||
did_load: bool; lib, did_load = os_lib_load( Path_Sectr_Module )
|
||||
file_copy_sync( Path_Sectr_Module, Path_Sectr_Live_Module, allocator = context.temp_allocator )
|
||||
did_load: bool; lib, did_load = os_lib_load( Path_Sectr_Live_Module )
|
||||
if ! did_load do panic( "Failed to load the sectr module.")
|
||||
startup = cast( type_of( host_memory.client_api.startup)) os_lib_get_proc(lib, "startup")
|
||||
tick_lane_startup = cast( type_of( host_memory.client_api.tick_lane_startup)) os_lib_get_proc(lib, "tick_lane_startup")
|
||||
job_worker_startup = cast( type_of( host_memory.client_api.job_worker_startup)) os_lib_get_proc(lib, "job_worker_startup")
|
||||
hot_reload = cast( type_of( host_memory.client_api.hot_reload)) os_lib_get_proc(lib, "hot_reload")
|
||||
tick_lane = cast( type_of( host_memory.client_api.tick_lane)) os_lib_get_proc(lib, "tick_lane")
|
||||
clean_frame = cast( type_of( host_memory.client_api.clean_frame)) os_lib_get_proc(lib, "clean_frame")
|
||||
jobsys_worker_tick = cast( type_of( host_memory.client_api.jobsys_worker_tick)) os_lib_get_proc(lib, "jobsys_worker_tick")
|
||||
if startup == nil do panic("Failed to load sectr.startup symbol" )
|
||||
if tick_lane_startup == nil do panic("Failed to load sectr.tick_lane_startup symbol" )
|
||||
if hot_reload == nil do panic("Failed to load sectr.hot_reload symbol" )
|
||||
if tick_lane == nil do panic("Failed to load sectr.tick_lane symbol" )
|
||||
if clean_frame == nil do panic("Failed to load sectr.clean_frmae symbol" )
|
||||
if startup == nil do panic("Failed to load sectr.startup symbol" )
|
||||
if tick_lane_startup == nil do panic("Failed to load sectr.tick_lane_startup symbol" )
|
||||
if job_worker_startup == nil do panic("Failed to load sectr.job_worker_startup symbol" )
|
||||
if hot_reload == nil do panic("Failed to load sectr.hot_reload symbol" )
|
||||
if tick_lane == nil do panic("Failed to load sectr.tick_lane symbol" )
|
||||
if clean_frame == nil do panic("Failed to load sectr.clean_frame symbol" )
|
||||
if jobsys_worker_tick == nil do panic("Failed to laod sectr.jobsys_worker_tick")
|
||||
lib_version = version_id
|
||||
return
|
||||
}
|
||||
@@ -132,6 +134,7 @@ main :: proc()
|
||||
thread_start(worker_thread)
|
||||
}
|
||||
}
|
||||
barrier_init(& host_memory.lane_job_sync, THREAD_TICK_LANES + THREAD_JOB_WORKERS)
|
||||
host_tick_lane()
|
||||
}
|
||||
|
||||
@@ -187,6 +190,7 @@ host_tick_lane :: proc()
|
||||
delta_ns = time_tick_lap_time( & host_tick )
|
||||
host_tick = time_tick_now()
|
||||
}
|
||||
leader := barrier_wait(& host_memory.lane_sync)
|
||||
host_lane_shutdown()
|
||||
}
|
||||
host_lane_shutdown :: proc()
|
||||
@@ -212,8 +216,11 @@ host_job_worker_entrypoint :: proc(worker_thread: ^SysThread)
|
||||
host_memory.client_api.jobsys_worker_tick()
|
||||
// TODO(Ed): We cannot allow job threads to enter the reload barrier until all jobs have drained.
|
||||
if sync_load(& host_memory.client_api_hot_reloaded, .Acquire) {
|
||||
leader :=barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
break
|
||||
// Signals to main hread when all jobs have drained.
|
||||
leader :=barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
// Job threads wait here until client module is back
|
||||
leader =barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
host_memory.client_api.hot_reload(& host_memory, & thread_memory)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,28 +239,25 @@ sync_client_api :: proc()
|
||||
profile("Master_Prepper: Reloading client module")
|
||||
sync_store(& host_memory.client_api_hot_reloaded, true, .Release)
|
||||
// We nee to wait for the job queue to drain.
|
||||
barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
leader = barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
{
|
||||
version_id := host_memory.client_api.lib_version + 1
|
||||
unload_client_api( & host_memory.client_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 ); {}
|
||||
|
||||
version_id := host_memory.client_api.lib_version + 1
|
||||
unload_client_api( & host_memory.client_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 )
|
||||
host_memory.client_api = load_client_api( version_id )
|
||||
verify( host_memory.client_api.lib_version != 0, "Failed to hot-reload the sectr module" )
|
||||
thread_sleep( Millisecond * 100 )
|
||||
|
||||
// Don't let jobs continue until after we clear loading.
|
||||
barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
host_memory.client_api = load_client_api( version_id )
|
||||
verify( host_memory.client_api.lib_version != 0, "Failed to hot-reload the sectr module" )
|
||||
}
|
||||
leader = barrier_wait(& host_memory.job_hot_reload_sync)
|
||||
}
|
||||
}
|
||||
leader = barrier_wait(& host_memory.lane_sync)
|
||||
leader = barrier_wait(& host_memory.lane_sync)
|
||||
// Lanes are safe to continue.
|
||||
if sync_load(& host_memory.client_api_hot_reloaded, .Acquire)
|
||||
{
|
||||
if sync_load(& host_memory.client_api_hot_reloaded, .Acquire) {
|
||||
host_memory.client_api.hot_reload(& host_memory, & thread_memory)
|
||||
if thread_memory.id == .Master_Prepper {
|
||||
sync_store(& host_memory.client_api_hot_reloaded, false, .Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
unload_client_api :: proc( module : ^Client_API )
|
||||
|
||||
@@ -18,6 +18,7 @@ ModuleAPI :: struct {
|
||||
|
||||
startup: type_of( startup ),
|
||||
tick_lane_startup: type_of( tick_lane_startup),
|
||||
job_worker_startup: type_of( job_worker_startup),
|
||||
hot_reload: type_of( hot_reload ),
|
||||
tick_lane: type_of( tick_lane ),
|
||||
clean_frame: type_of( clean_frame),
|
||||
@@ -47,21 +48,46 @@ Threads will eventually return to their tick_lane upon completion.
|
||||
@export
|
||||
hot_reload :: proc(host_mem: ^ProcessMemory, thread_mem: ^ThreadMemory)
|
||||
{
|
||||
profile(#procedure)
|
||||
thread = thread_mem
|
||||
if thread.id == .Master_Prepper {
|
||||
grime_set_profiler_module_context(& memory.spall_context)
|
||||
sync_store(& memory, host_mem, .Release)
|
||||
// Critical reference synchronization
|
||||
{
|
||||
thread = thread_mem
|
||||
if thread.id == .Master_Prepper {
|
||||
sync_store(& memory, host_mem, .Release)
|
||||
grime_set_profiler_module_context(& memory.spall_context)
|
||||
}
|
||||
else {
|
||||
for ; memory == nil; {
|
||||
sync_load(& memory, .Acquire)
|
||||
}
|
||||
for ; thread == nil; {
|
||||
thread = thread_mem
|
||||
}
|
||||
}
|
||||
grime_set_profiler_thread_buffer(& thread.spall_buffer)
|
||||
}
|
||||
profile(#procedure)
|
||||
// Do hot-reload stuff...
|
||||
{
|
||||
|
||||
}
|
||||
// Critical reference synchronization
|
||||
{
|
||||
leader := barrier_wait(& memory.lane_job_sync)
|
||||
if thread.id == .Master_Prepper {
|
||||
sync_store(& memory.client_api_hot_reloaded, false, .Release)
|
||||
}
|
||||
else {
|
||||
for ; memory.client_api_hot_reloaded == true; {
|
||||
sync_load(& memory.client_api_hot_reloaded, .Acquire)
|
||||
}
|
||||
}
|
||||
leader = barrier_wait(& memory.lane_job_sync)
|
||||
}
|
||||
grime_set_profiler_thread_buffer(& thread.spall_buffer)
|
||||
}
|
||||
|
||||
/*
|
||||
Called by host_tick_lane_startup
|
||||
Used for lane specific startup operations
|
||||
|
||||
The lane tick cannot be handled it, its call must be done by the host module.
|
||||
(We need threads to not be within a client callstack in the even of a hot-reload)
|
||||
*/
|
||||
@export
|
||||
tick_lane_startup :: proc(thread_mem: ^ThreadMemory)
|
||||
@@ -73,8 +99,19 @@ tick_lane_startup :: proc(thread_mem: ^ThreadMemory)
|
||||
profile(#procedure)
|
||||
}
|
||||
|
||||
/*
|
||||
@export
|
||||
job_worker_startup :: proc(thread_mem: ^ThreadMemory)
|
||||
{
|
||||
if thread_mem.id != .Master_Prepper {
|
||||
thread = thread_mem
|
||||
grime_set_profiler_thread_buffer(& thread.spall_buffer)
|
||||
}
|
||||
profile(#procedure)
|
||||
}
|
||||
|
||||
/*
|
||||
Host handles the loop.
|
||||
(We need threads to be outside of client callstack in the event of a hot-reload)
|
||||
*/
|
||||
@export
|
||||
tick_lane :: proc(host_delta_time_ms: f64, host_delta_ns: Duration) -> (should_close: b64 = false)
|
||||
|
||||
@@ -17,39 +17,42 @@ ProcessMemory :: struct {
|
||||
// Host
|
||||
host_persist_buf: [32 * Mega]byte,
|
||||
host_scratch_buf: [64 * Mega]byte,
|
||||
host_persist: Odin_Arena,
|
||||
host_scratch: Odin_Arena,
|
||||
host_api: Host_API,
|
||||
host_persist: Odin_Arena, // Host Persistent (Non-Wipeable), for bad third-party static object allocation
|
||||
host_scratch: Odin_Arena, // Host Temporary Wipable
|
||||
host_api: Host_API, // Client -> Host Interface
|
||||
|
||||
// Textual Logging
|
||||
logger: Logger,
|
||||
logger: Logger,
|
||||
path_logger_finalized: string,
|
||||
|
||||
// Profiling
|
||||
spall_context: Spall_Context,
|
||||
// TODO(Ed): Try out Superluminal's API!
|
||||
|
||||
// Multi-threading
|
||||
threads: [MAX_THREADS](^SysThread),
|
||||
job_system: JobSystemContext,
|
||||
tick_lanes: int,
|
||||
lane_sync: sync.Barrier,
|
||||
job_hot_reload_sync: sync.Barrier, // Used to sync jobs with main thread during hot-reload junction.
|
||||
tick_running: b64,
|
||||
threads: [MAX_THREADS](^SysThread), // All threads are tracked here.
|
||||
job_system: JobSystemContext, // State tracking for job system.
|
||||
tick_running: b64, // When disabled will lead to shutdown of the process.
|
||||
tick_lanes: int, // Runtime tracker of live tick lane threads
|
||||
lane_sync: sync.Barrier, // Used to sync tick lanes during wide junctions.
|
||||
job_hot_reload_sync: sync.Barrier, // Used to sync jobs with main thread during hot-reload junction.
|
||||
lane_job_sync: sync.Barrier, // Used to sync tick lanes and job workers during hot-reload.
|
||||
|
||||
// Client Module
|
||||
client_api_hot_reloaded: b64,
|
||||
client_api: ModuleAPI,
|
||||
client_memory: State,
|
||||
client_api_hot_reloaded: b64, // Used to signal to threads when hot-reload paths should be taken.
|
||||
client_api: ModuleAPI, // Host -> Client Interface
|
||||
client_memory: State,
|
||||
}
|
||||
|
||||
Host_API :: struct {
|
||||
request_virtual_memory: #type proc(),
|
||||
request_virtual_mapped_io: #type proc(),
|
||||
request_virtual_memory: #type proc(), // All dynamic allocations will utilize vmem interfaces
|
||||
request_virtual_mapped_io: #type proc(), // TODO(Ed): Figure out usage constraints of this.
|
||||
}
|
||||
|
||||
ThreadMemory :: struct {
|
||||
using _: ThreadWorkerContext,
|
||||
|
||||
// Per-thread profiling
|
||||
spall_buffer_backing: [SPALL_BUFFER_DEFAULT_SIZE * 2]byte,
|
||||
spall_buffer: Spall_Buffer,
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ import "core:prof/spall"
|
||||
Spall_Buffer :: spall.Buffer
|
||||
|
||||
import "core:sync"
|
||||
AtomicMutex :: sync.Atomic_Mutex
|
||||
sync_store :: sync.atomic_store_explicit
|
||||
sync_load :: sync.atomic_load_explicit
|
||||
AtomicMutex :: sync.Atomic_Mutex
|
||||
barrier_wait :: sync.barrier_wait
|
||||
sync_store :: sync.atomic_store_explicit
|
||||
sync_load :: sync.atomic_load_explicit
|
||||
|
||||
import threading "core:thread"
|
||||
SysThread :: threading.Thread
|
||||
@@ -43,7 +44,9 @@ import threading "core:thread"
|
||||
thread_start :: threading.start
|
||||
|
||||
import "core:time"
|
||||
Duration :: time.Duration
|
||||
Millisecond :: time.Millisecond
|
||||
Duration :: time.Duration
|
||||
thread_sleep :: time.sleep
|
||||
|
||||
import "codebase:grime"
|
||||
Logger :: grime.Logger
|
||||
|
||||
Reference in New Issue
Block a user