WIP: fleshing out cod2 for once a bit more

Planning to try out a flavor of Ryan's multi-threaded laned procs with the some extra threads hooked up separately to a job system.
Will most likely do 2 threads main/helper on live lanes, then 2 others on job queue loops
This commit is contained in:
2025-10-11 19:17:29 -04:00
parent 7219b780fc
commit 05e979907a
20 changed files with 387 additions and 37 deletions

View File

@@ -2,16 +2,19 @@ package vefontcache
// Add profiling hookup here
// import ""
import "codebase:grime"
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
grime.profile_begin(name, loc)
}
@(disabled = DISABLE_PROFILING)
profile_end :: #force_inline proc "contextless" () {
grime.profile_end()
}

7
code2/grime/Readme.md Normal file
View File

@@ -0,0 +1,7 @@
# Grime
This is a top-level package to adjust odin to my personalized usage.
I curate all usage of odin's provided package definitons through here. The client and host packages should never directly import them.
There are no implicit static allocations in Grime. Ideally there are also none from the base/core packages but some probably leak.

1
code2/grime/arenas.odin Normal file
View File

@@ -0,0 +1 @@
package grime

8
code2/grime/context.odin Normal file
View File

@@ -0,0 +1,8 @@
package grime
// Context :: struct {
// }
// context_usr :: #force_inline proc( $ Type : typeid ) -> (^Type) {
// return cast(^Type) context.user_ptr
// }

View File

@@ -0,0 +1,20 @@
package grime
sll_stack_push_n :: proc "contextless" (curr, n, n_link: ^^$Type) {
(n_link ^) = (curr ^)
(curr ^) = (n ^)
}
sll_queue_push_nz :: proc "contextless" (first: ^$ParentType, last, n: ^^$Type, nil_val: ^Type) {
if (first ^) == nil_val {
(first ^) = n^
(last ^) = n^
n^.next = nil_val
}
else {
(last ^).next = n^
(last ^) = n^
n^.next = nil_val
}
}
sll_queue_push_n :: #force_inline proc "contextless" (first: $ParentType, last, n: ^^$Type) { sll_queue_push_nz(first, last, n, nil) }

226
code2/grime/memory.odin Normal file
View File

@@ -0,0 +1,226 @@
package grime
Kilo :: 1024
Mega :: Kilo * 1024
Giga :: Mega * 1024
Tera :: Giga * 1024
ptr_cursor :: #force_inline proc "contextless" (ptr: ^$Type) -> [^]Type { return transmute([^]Type) ptr }
align_pow2 :: proc(x: int, b: int) -> int {
assert(b != 0)
assert((b & (b - 1)) == 0) // Check power of 2
return ((x + b - 1) & ~(b - 1))
}
memory_zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr {
mem_zero_volatile(data, len) // Use the volatile mem_zero
atomic_thread_fence(.Seq_Cst) // Prevent reordering
return data
}
memory_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr {
mem_copy(dst, src, len)
return dst
}
SliceByte :: struct {
data: [^]byte,
len: int
}
SliceRaw :: struct ($Type: typeid) {
data: [^]Type,
len: int,
}
slice :: #force_inline proc "contextless" (s: [^] $Type, num: $Some_Integer) -> [ ]Type { return transmute([]Type) SliceRaw(Type) { s, cast(int) num } }
slice_cursor :: #force_inline proc "contextless" (s: []$Type) -> [^]Type { return transmute([^]Type) raw_data(s) }
slice_assert :: #force_inline proc (s: $SliceType / []$Type) {
assert(len(s) > 0)
assert(s != nil)
}
slice_end :: #force_inline proc "contextless" (s : $SliceType / []$Type) -> ^Type { return & cursor(s)[len(s)] }
slice_copy :: proc "contextless" (dst, src: $SliceType / []$Type) -> int {
n := max(0, min(len(dst), len(src)))
if n > 0 {
mem_copy(raw_data(dst), raw_data(src), n * size_of(Type))
}
return n
}
@(require_results) slice_to_bytes :: proc "contextless" (s: []$Type) -> []byte { return ([^]byte)(raw_data(s))[:len(s) * size_of(Type)] }
@(require_results) slice_raw :: proc "contextless" (s: []$Type) -> SliceRaw(Type) { return transmute(SliceRaw(Type)) s }
//region Allocator Interface
AllocatorOp :: enum u32 {
Alloc_NoZero = 0, // If Alloc exist, so must No_Zero
Alloc,
Free,
Reset,
Grow_NoZero,
Grow,
Shrink,
Rewind,
SavePoint,
Query, // Must always be implemented
}
AllocatorQueryFlag :: enum u64 {
Alloc,
Free,
Reset, // Wipe the allocator's state
Shrink,
Grow,
Resize, // Supports both grow and shrink
Rewind, // Ability to rewind to a save point (ex: arenas, stack), must also be able to save such a point
// Actually_Resize,
// Is_This_Yours,
Hint_Fast_Bump,
Hint_General_Heap,
Hint_Per_Frame_Temporary,
Hint_Debug_Support,
}
AllocatorQueryFlags :: bit_set[AllocatorQueryFlag; u64]
AllocatorSP :: struct {
type_sig: AllocatorProc,
slot: int,
}
AllocatorProc :: #type proc (input: AllocatorProc_In, out: ^AllocatorProc_Out)
AllocatorProc_In :: struct {
data: rawptr,
requested_size: int,
alignment: int,
using _ : struct #raw_union {
old_allocation: []byte,
save_point : AllocatorSP,
},
op: AllocatorOp,
}
AllocatorProc_Out :: struct {
using _ : struct #raw_union {
allocation: []byte,
save_point: AllocatorSP,
},
features: AllocatorQueryFlags,
left: int,
max_alloc: int,
min_alloc: int,
continuity_break: b32,
}
AllocatorQueryInfo :: struct {
save_point: AllocatorSP,
features: AllocatorQueryFlags,
left: int,
max_alloc: int,
min_alloc: int,
continuity_break: b32,
}
AllocatorInfo :: struct {
procedure: AllocatorProc,
data: rawptr,
}
// #assert(size_of(AllocatorQueryInfo) == size_of(AllocatorProc_Out))
MEMORY_ALIGNMENT_DEFAULT :: 2 * size_of(rawptr)
allocator_query :: proc(ainfo := context.allocator) -> AllocatorQueryInfo {
assert(ainfo.procedure != nil)
out: AllocatorQueryInfo; (cast(AllocatorProc)ainfo.procedure)({data = ainfo.data, op = .Query}, transmute(^AllocatorProc_Out) & out)
return out
}
mem_free :: proc(mem: []byte, ainfo := context.allocator) {
assert(ainfo.procedure != nil)
(cast(AllocatorProc)ainfo.procedure)({data = ainfo.data, op = .Free, old_allocation = mem}, & {})
}
mem_reset :: proc(ainfo := context.allocator) {
assert(ainfo.procedure != nil)
(cast(AllocatorProc)ainfo.procedure)({data = ainfo.data, op = .Reset}, &{})
}
mem_rewind :: proc(ainfo := context.allocator, save_point: AllocatorSP) {
assert(ainfo.procedure != nil)
(cast(AllocatorProc)ainfo.procedure)({data = ainfo.data, op = .Rewind, save_point = save_point}, & {})
}
mem_save_point :: proc(ainfo := context.allocator) -> AllocatorSP {
assert(ainfo.procedure != nil)
out: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)({data = ainfo.data, op = .SavePoint}, & out)
return out.save_point
}
mem_alloc :: proc(size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = no_zero ? .Alloc_NoZero : .Alloc,
requested_size = size,
alignment = alignment,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return output.allocation
}
mem_grow :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = no_zero ? .Grow_NoZero : .Grow,
requested_size = size,
alignment = alignment,
old_allocation = mem,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return output.allocation
}
mem_resize :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = len(mem) < size ? .Shrink : no_zero ? .Grow_NoZero : .Grow,
requested_size = size,
alignment = alignment,
old_allocation = mem,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return output.allocation
}
mem_shrink :: proc(mem: []byte, size: int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []byte {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = .Shrink,
requested_size = size,
alignment = alignment,
old_allocation = mem,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return output.allocation
}
alloc_type :: proc($Type: typeid, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> ^Type {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = no_zero ? .Alloc_NoZero : .Alloc,
requested_size = size_of(Type),
alignment = alignment,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return transmute(^Type) raw_data(output.allocation)
}
alloc_slice :: proc($SliceType: typeid / []$Type, num : int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false, ainfo := context.allocator) -> []Type {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
op = no_zero ? .Alloc_NoZero : .Alloc,
requested_size = size_of(Type) * num,
alignment = alignment,
}
output: AllocatorProc_Out
(cast(AllocatorProc)ainfo.procedure)(input, & output)
return transmute([]Type) slice(raw_data(output.allocation), num)
}
//endregion Allocator Interface

1
code2/grime/os.odin Normal file
View File

@@ -0,0 +1 @@
package grime

View File

@@ -0,0 +1,23 @@
package grime
import "base:builtin"
Odin_OS_Type :: type_of(ODIN_OS)
import "base:intrinsics"
atomic_thread_fence :: intrinsics.atomic_thread_fence
mem_zero :: intrinsics.mem_zero
mem_zero_volatile :: intrinsics.mem_zero_volatile
mem_copy :: intrinsics.mem_copy_non_overlapping
mem_copy_overlapping :: intrinsics.mem_copy
import "base:runtime"
Assertion_Failure_Proc :: runtime.Assertion_Failure_Proc
Logger :: runtime.Logger
Random_Generator :: runtime.Random_Generator
slice_copy_overlapping :: runtime.copy_slice
import core_os "core:os"
// ODIN_OS :: core_os.ODIN_OS
import "core:slice"
slice_zero :: slice.zero

3
code2/host/Readme.md Normal file
View File

@@ -0,0 +1,3 @@
# Host Module
The sole job of this module is to provide a bare launch pad and runtime module hot-reload support for the client module (sectr). To achieve this the static memory of the client module is tracked by the host and provides an api for the client to reload itself when a change is detected. The client is reponsible for populating the static memory reference and doing anything else it needs via the host api that it cannot do on its own.

View File

@@ -1,8 +0,0 @@
package host
main :: proc()
{
}

24
code2/host/launch.odin Normal file
View File

@@ -0,0 +1,24 @@
package host
Path_Logs :: "../logs"
when ODIN_OS == .Windows
{
Path_Sectr_Module :: "sectr.dll"
Path_Sectr_Live_Module :: "sectr_live.dll"
Path_Sectr_Debug_Symbols :: "sectr.pdb"
}
// Only static memory host has.
host_memory: HostMemory
main :: proc()
{
host_memory.host_api.sync_client_module = sync_client_api
host_memory.client_api.startup(& host_memory)
}
@export
sync_client_api :: proc()
{
// Fill out detection and reloading of client api.
}

View File

@@ -0,0 +1,24 @@
package host
import "base:builtin"
// Odin_OS_Type :: type_of(ODIN_OS)
import "base:intrinsics"
// atomic_thread_fence :: intrinsics.atomic_thread_fence
// mem_zero :: intrinsics.mem_zero
// mem_zero_volatile :: intrinsics.mem_zero_volatile
// mem_copy :: intrinsics.mem_copy_non_overlapping
// mem_copy_overlapping :: intrinsics.mem_copy
import "base:runtime"
// Assertion_Failure_Proc :: runtime.Assertion_Failure_Proc
// Logger :: runtime.Logger
import core_os "core:os"
import grime "codebase:grime"
import "codebase:sectr"
Client_API :: sectr.ModuleAPI
HostMemory :: sectr.HostMemory

View File

@@ -1,2 +1,3 @@
# Sectr Package

View File

@@ -12,23 +12,21 @@ ModuleAPI :: struct {
lib: dynlib.Library,
// write-time: FileTime,
startup: type_of( startup ),
shutdown: type_of( sectr_shutdown ),
reload: type_of( hot_reload ),
tick: type_of( tick ),
clean_frame: type_of( clean_frame ),
startup: type_of( startup ),
hot_reload: type_of( hot_reload ),
}
StartupContext :: struct {}
@export
startup :: proc(ctx: StartupContext)
startup :: proc(host_mem: ^HostMemory)
{
thread_wide_startup()
}
@export
sectr_shutdown :: proc()
thread_wide_startup :: proc()
{
}
@@ -38,15 +36,3 @@ hot_reload :: proc()
{
}
@export
tick :: proc()
{
}
@export
clean_frame ::proc()
{
}

View File

@@ -0,0 +1,16 @@
package sectr
HostMemory :: struct {
client_api: ModuleAPI,
client_memory: ^State,
host_api: Host_API,
}
Host_API :: struct {
launch_thread: #type proc(),
request_virtual_memory: #type proc(),
request_virtual_mapped_io: #type proc(),
sync_client_module : #type proc(),
}

View File

@@ -1 +1,3 @@
package sectr

View File

@@ -1 +1,9 @@
package sectr
// This should be the only global on client module side.
host_memory: ^HostMemory
State :: struct {
}

View File

@@ -66,10 +66,12 @@ $command_run = 'run'
$flag_build_mode = '-build-mode:'
$flag_build_mode_dll = '-build-mode:dll'
$flag_build_diagnostics = '-build-diagnostics'
$flag_collection = '-collection:'
$flag_debug = '-debug'
$flag_define = '-define:'
$flag_default_allocator_nil = '-default-to-nil-allocator'
$flag_default_allocator_panic = '-default-to-panic-allocator'
$flag_disable_assert = '-disable-assert'
$flag_dynamic_map_calls = '-dynamic-map-calls'
$flag_extra_assembler_flags = '-extra_assembler-flags:'
@@ -139,7 +141,7 @@ push-location $path_root
function build-prototype
{
push-location $path_code
$project_name = 'sectr2'
$project_name = 'sectr'
write-host "`nBuilding Sectr Prototype`n"
@@ -213,24 +215,26 @@ push-location $path_root
$build_args += $flag_microarch_zen5
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_none
$build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'
# $build_args += $flag_show_system_calls
$build_args += $flag_show_timings
$build_args += ($flag_extra_linker_flags + $linker_args )
# $build_args += $flag_no_bounds_check
# $build_args += $flag_no_thread_checker
# $build_args += $flag_dynamic_map_calls
$build_args += $flag_default_allocator_nil
# $build_args += $flag_default_allocator_nil
$build_args += $flag_default_allocator_panic
$build_args += ($flag_max_error_count + '10')
# $build_args += $flag_sanitize_address
# $build_args += $flag_sanitize_memory
# $build_args += $flag_show_debug_messages
$build_args += $flag_show_timings
# $build_args += $flag_build_diagnostics
# TODO(Ed): Enforce nil default allocator
# foreach ($arg in $build_args) {
@@ -297,8 +301,8 @@ push-location $path_root
# $build_args += $flag_micro_architecture_native
$build_args += $flag_microarch_zen5
$build_args += $flag_thread_count + $CoreCount_Physical
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_none
$build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
@@ -309,10 +313,11 @@ push-location $path_root
# $build_args += $flag_show_system_call
# $build_args += $flag_no_bounds_check
# $build_args += $flag_no_thread_checker
$build_args += $flag_default_allocator_nil
$build_args += $flag_default_allocator_panic
$build_args += ($flag_max_error_count + '10')
# $build_args += $flag_sanitize_address
# $build_args += $flag_sanitize_memory
# $build_args += $flag_build_diagnostics
# TODO(Ed): Enforce nil default allocator
# foreach ($arg in $build_args) {