From 6d780482c75d3e33b5d85b7444a8acb018832a6a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 7 Jul 2025 02:00:57 -0400 Subject: [PATCH] Mostly still reviewing and planning... (see description) Anything considered static can be aggregated into a single VArena. We don't have to worry about ever releasing its memory or it growing "too large". All memory here must be fixed sized. Conservative persistent memory can grow on demand but we would perfer if it could be trimmed or released when no longer dealing with heavy scenarios. Persistent memory should use a slab allocator that is backed by a virtual address space pool allocator instead of pools allocating from a single varena. Chained Arenas can source thier chunks of vmem from the slab which can be utilized for scratch memory. Fonts should be loaded from VSlab. The string cache should use a dedicated varena with 16-byte alignment. All conservative memory should be trimmable by a wipe command which should free all unused blocks. Each block should be a single OS aware reserve of vmem. The Frame can possilby stay as a single varena with scratch allocation utilized on demand. Although it may be more viable for chained varenas to be derived from the main varena via a slab or pool interface. Frame memory should be trimmable on command which should release its committed vmem to its initial value. A dedicated transient varena should not exist. It should be removed when possible. File mappings for now can use a dedicated varena made on demand with a capped reserve size of 4 meg. Any file exceeding this needs the host to support virtual memory mapped I/O for files. The codebase db will use sqlite for the file I/O abstraction. Host might only need to track the first persistent block of vmem, and the rest can be handled by the client (including wrapping that vmem up in a varena). Hot-reload only needs persistent vmem's ref restored on the client module's side. All other references can be resolved from there. --- code/grime/virtual_arena.odin | 9 +- code/host/host.odin | 3 + code/sectr/app/state.odin | 48 +--- code/sectr/engine/client_api.odin | 1 + code/sectr/engine/host_api.odin | 8 + code/sectr/engine/job_system.odin | 75 ++++++ code/sectr/grime/pkg_mappings.odin | 8 + code/sectr/ui/core/layout_compute.odin | 316 ++++++++++++------------- 8 files changed, 265 insertions(+), 203 deletions(-) create mode 100644 code/sectr/engine/host_api.odin create mode 100644 code/sectr/engine/job_system.odin diff --git a/code/grime/virtual_arena.odin b/code/grime/virtual_arena.odin index edf0f34..1633bd9 100644 --- a/code/grime/virtual_arena.odin +++ b/code/grime/virtual_arena.odin @@ -62,7 +62,10 @@ varena_allocator :: proc( arena : ^VArena ) -> ( allocator : Allocator ) { // Default growth_policy is nil varena_init :: proc( base_address : uintptr, to_reserve, to_commit : uint, - growth_policy : VArena_GrowthPolicyProc, allow_any_resize : b32 = false, dbg_name : string, enable_mem_tracking : b32 = false, + growth_policy: VArena_GrowthPolicyProc = nil, + allow_any_resize: b32 = false, + dbg_name: string = "", + enable_mem_tracking: b32 = false, ) -> ( arena : VArena, alloc_error : AllocatorError) { page_size := uint(virtual_get_page_size()) @@ -78,8 +81,8 @@ varena_init :: proc( base_address : uintptr, to_reserve, to_commit : uint, return } - arena.vmem = vmem - arena.commit_used = 0 + arena.vmem = vmem + arena.commit_used = 0 if growth_policy == nil { arena.growth_policy = varena_default_growth_policy diff --git a/code/host/host.odin b/code/host/host.odin index 3a10efe..4af76c8 100644 --- a/code/host/host.odin +++ b/code/host/host.odin @@ -71,6 +71,7 @@ import "codebase:grime" import "codebase:sectr" VArena :: sectr.VArena fatal :: sectr.fatal + JobSystemContext :: sectr.JobSystemContext Logger :: sectr.Logger logger_init :: sectr.logger_init LogLevel :: sectr.LogLevel @@ -106,6 +107,8 @@ RuntimeState :: struct { running : b32, client_memory : ClientMemory, sectr_api : sectr.ModuleAPI, + + job_system: JobSystemContext, } ClientMemory :: struct { diff --git a/code/sectr/app/state.odin b/code/sectr/app/state.odin index a2ccc83..b0059e9 100644 --- a/code/sectr/app/state.odin +++ b/code/sectr/app/state.odin @@ -10,8 +10,11 @@ Str_App_State := "App State" //region Memory +// Data segment Memory for sectr module. Memory_App : Memory +// General memory configuration + Memory_Base_Address_Persistent :: Terabyte * 1 Memory_Base_Address_Frame :: Memory_Base_Address_Persistent + Memory_Reserve_Persistent * 2 Memory_Base_Address_Transient :: Memory_Base_Address_Frame + Memory_Reserve_Frame * 2 @@ -29,13 +32,6 @@ Memory_Commit_Initial_Frame :: 4 * Kilobyte Memory_Commit_Initial_Transient :: 4 * Kilobyte Memory_Commit_Initial_Filebuffer :: 4 * Kilobyte -MemorySnapshot :: struct { - persistent : []u8, - frame : []u8, - transient : []u8, - // files_buffer cannot be restored from snapshot -} - Memory :: struct { persistent : ^VArena, frame : ^VArena, @@ -44,15 +40,12 @@ Memory :: struct { state : ^State, - // Should only be used for small memory allocation iterations - // Not for large memory env states - snapshot : MemorySnapshot, - replay : ReplayState, logger : Logger, profiler : ^SpallProfiler } + persistent_allocator :: proc() -> Allocator { result := varena_allocator( Memory_App.persistent ) return result @@ -97,37 +90,6 @@ transient_slab_allocator :: proc() -> Allocator { return result } -// TODO(Ed) : Implment host memory mapping api -save_snapshot :: proc( snapshot : ^MemorySnapshot ) -{ - // Make sure the snapshot size is able to hold the current size of the arenas - // Grow the files & mapping otherwise - { - // TODO(Ed) : Implement eventually - } - - persistent := Memory_App.persistent - mem.copy_non_overlapping( & snapshot.persistent[0], persistent.reserve_start, int(persistent.commit_used) ) - - frame := Memory_App.frame - mem.copy_non_overlapping( & snapshot.frame[0], frame.reserve_start, int(frame.commit_used) ) - - transient := Memory_App.transient - mem.copy_non_overlapping( & snapshot.transient[0], transient.reserve_start, int(transient.commit_used) ) -} - -// TODO(Ed) : Implment host memory mapping api -load_snapshot :: proc( snapshot : ^MemorySnapshot ) { - persistent := Memory_App.persistent - mem.copy_non_overlapping( persistent.reserve_start, & snapshot.persistent[0], int(persistent.commit_used) ) - - frame := Memory_App.frame - mem.copy_non_overlapping( frame.reserve_start, & snapshot.frame[0], int(frame.commit_used) ) - - transient := Memory_App.transient - mem.copy_non_overlapping( transient.reserve_start, & snapshot.transient[0], int(transient.commit_used) ) -} - // TODO(Ed) : Implement usage of this MemoryConfig :: struct { reserve_persistent : uint, @@ -220,6 +182,8 @@ State :: struct { transient_clear_time : f32, // Time in seconds for the usual period to clear transient transient_clear_elapsed : f32, // Time since last clear + job_system : JobSystemContext, + string_cache : StringCache, input_data : [2]InputState, diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index e184de7..79beff7 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -626,6 +626,7 @@ clean_frame :: proc() free_all( frame_allocator() ) + // TODO(Ed): Delete this we are no longer using the temp_allocator this way. transient_clear_elapsed += frametime_delta32() if transient_clear_elapsed >= transient_clear_time && ! transinet_clear_lock { diff --git a/code/sectr/engine/host_api.odin b/code/sectr/engine/host_api.odin new file mode 100644 index 0000000..f1cec99 --- /dev/null +++ b/code/sectr/engine/host_api.odin @@ -0,0 +1,8 @@ +package sectr + + +Host_API :: struct { + // request_virtual_memory: HostAPI_RequestVirtualMemory, + // request_virtual_mapped_io: HostAPI_RequestVirtaulMappedIO, + // enqueue_job: , +} diff --git a/code/sectr/engine/job_system.odin b/code/sectr/engine/job_system.odin new file mode 100644 index 0000000..3cc73ce --- /dev/null +++ b/code/sectr/engine/job_system.odin @@ -0,0 +1,75 @@ +package sectr + +ThreadProc :: #type proc(data: rawptr) + +IgnoredThreads :: bit_set[ 0 ..< 64 ] + +JobProc :: #type proc(data: rawptr) + +JobGroup :: struct { + counter: u64, +} + +JobPriority :: enum { + Medium = 0, + Low, + High, +} + +Job :: struct { + next: ^Job, + cb: JobProc, + data: rawptr, + group: ^JobGroup, + ignored: IgnoredThreads, + dbg_lbl: string, +} + +JobList :: struct { + head: ^Job, + mutex: AtomicMutex, +} + +JobSystemContext :: struct { + job_lists: [JobPriority]JobList, + worker_cb: ThreadProc, + worker_data: rawptr, + counter: int, + workers: [] ^ThreadWorkerContext, + running: b32, +} + +ThreadWorkerContext :: struct { + system_ctx: Thread, + index: int, +} + +// Hard constraint for Windows +JOB_SYSTEM_MAX_WORKER_THREADS :: 64 + +/* +Threads are setup upfront during the client API's startup. + + +*/ + +jobsys_startup :: proc(ctx: ^JobSystemContext, num_workers : int, worker_exec: ThreadProc, worker_data: rawptr) { + ctx^ = { + worker_cb = worker_exec, + worker_data = worker_data, + counter = 1, + } + // Determine number of physical cores + // Allocate worker contextes based on number of physical cores - 1 (main thread managed by host included assumed to be index 0) + // + // num_hw_threads = min(JOB_SYSTEM_MAX_WORKER_THREADS, ) + // jobsys_worker_make : +} + +thread_worker_exec :: proc(_: rawptr) { + +} + +jobsys_shutdown :: proc(ctx: ^JobSystemContext) { + +} diff --git a/code/sectr/grime/pkg_mappings.odin b/code/sectr/grime/pkg_mappings.odin index 9afe3dd..ef26aac 100644 --- a/code/sectr/grime/pkg_mappings.odin +++ b/code/sectr/grime/pkg_mappings.odin @@ -126,6 +126,9 @@ import "core:path/filepath" import "core:slice" +import "core:sync" + AtomicMutex :: sync.Atomic_Mutex + import "core:strconv" parse_f32 :: strconv.parse_f32 parse_u64 :: strconv.parse_u64 @@ -147,6 +150,9 @@ import "core:time" time_now :: time.now Time :: time.Time +import "core:thread" + Thread :: thread.Thread + import "core:unicode" is_white_space :: unicode.is_white_space @@ -348,6 +354,8 @@ import "codebase:grime" varena_allocator :: grime.varena_allocator + VArena_GrowthPolicyProc :: grime.VArena_GrowthPolicyProc + //endregion codebase //region Procedure overload mappings diff --git a/code/sectr/ui/core/layout_compute.odin b/code/sectr/ui/core/layout_compute.odin index cd94856..1251493 100644 --- a/code/sectr/ui/core/layout_compute.odin +++ b/code/sectr/ui/core/layout_compute.odin @@ -1,163 +1,5 @@ package sectr -ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_LayoutDirection_X, width_ref : ^f32 = nil ) -{ - container_width : f32 - if width_ref != nil { - container_width = width_ref ^ - } - else { - container_width = container.computed.content.max.x - container.computed.content.min.x - } - container_height := container.computed.content.max.y - container.computed.content.min.y - - // do layout calculations for the children - total_stretch_ratio : f32 = 0.0 - size_req_children : f32 = 0 - for child := container.first; child != nil; child = child.next - { - using child.layout - scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) - if .Scale_Width_By_Height_Ratio in flags - { - size_req_children += size.min.x * container_height - continue - } - if .Fixed_Width in flags - { - size_req_children += size.min.x - continue - } - - size_req_children += size.min.x - total_stretch_ratio += anchor.ratio.x - } - - avail_flex_space := container_width - size_req_children - - allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space, container_height : f32 ) -> (space_allocated : f32) - { - using child.layout - if .Scale_Width_By_Height_Ratio in flags { - size.min.y = container_height - space_allocated = size.min.x * container_height - } - else if ! (.Fixed_Width in flags) { - potential_size := anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space - space_allocated = max(potential_size, size.min.x) - size.min.x = space_allocated - } - else { - space_allocated = size.min.x - } - space_allocated -= margins.left - margins.right - size.min.x -= margins.left - margins.right - flags |= {.Fixed_Width} - return - } - - space_used : f32 = 0.0 - switch direction{ - case .Left_To_Right: - for child := container.first; child != nil; child = child.next { - using child.layout - child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height) - anchor = range2({0, anchor.bottom}, {0, anchor.top}) - alignment = {0, alignment.y} - pos.x = space_used - space_used += child_width + child.layout.margins.left + child.layout.margins.right - } - case .Right_To_Left: - for child := container.first; child != nil; child = child.next { - using child.layout - child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height) - anchor = range2({1, anchor.bottom}, {0, anchor.top}) - alignment = {1, alignment.y} - pos.x = space_used - space_used -= child_width + child.layout.margins.left + child.layout.margins.right - } - } -} - -ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_LayoutDirection_Y, height_ref : ^f32 = nil ) -{ - container_height : f32 - if height_ref != nil { - container_height = height_ref ^ - } - else { - container_height = container.computed.content.max.y - container.computed.content.min.y - } - container_width := container.computed.content.max.x - container.computed.content.min.x - - // do layout calculations for the children - total_stretch_ratio : f32 = 0.0 - size_req_children : f32 = 0 - for child := container.first; child != nil; child = child.next - { - using child.layout - scaled_height_by_width : b32 = b32(.Scale_Height_By_Width_Ratio in flags) - if scaled_height_by_width { - size_req_children += size.min.y * container_width - continue - } - if .Fixed_Height in flags - { - size_req_children += size.min.y - continue - } - - size_req_children += size.min.y - total_stretch_ratio += anchor.ratio.y - } - - avail_flex_space := container_height - size_req_children - - allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space, container_width : f32 ) -> (space_allocated : f32) - { - using child.layout - if .Scale_Height_By_Width_Ratio in flags { - size.min.x = container_width - space_allocated = size.min.y * container_width - } - if ! (.Fixed_Height in flags) { - potential_size := (anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space) - space_allocated = max(potential_size, size.min.y) - size.min.y = space_allocated - } - else { - space_allocated = size.min.y - } - space_allocated -= margins.top - margins.bottom - size.min.y -= margins.top - margins.bottom - flags |= {.Fixed_Height} - return - } - - space_used : f32 = 0 - switch direction - { - case .Top_To_Bottom: - for child := container.first; child != nil; child = child.next { - using child.layout - child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width) - anchor = range2({anchor.left, 1}, {anchor.right, 0}) - alignment = {alignment.x, 1} - pos.y = space_used - space_used -= child_height - child.layout.margins.top - child.layout.margins.bottom - } - case .Bottom_To_Top: - for child := container.first; child != nil; child = child.next { - using child.layout - child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width) - anchor = range2({anchor.left,0}, {anchor.right, 0}) - alignment = {alignment.x, 0} - pos.y = space_used - space_used += child_height - child.layout.margins.top - child.layout.margins.bottom - } - } -} - ui_box_compute_layout :: proc( box : ^UI_Box, dont_mark_fresh : b32 = false, ancestors_layout_required : b32 = false, @@ -410,3 +252,161 @@ ui_box_compute_layout_children :: proc( box : ^UI_Box ) ui_box_compute_layout( current ) } } + +ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_LayoutDirection_X, width_ref : ^f32 = nil ) +{ + container_width : f32 + if width_ref != nil { + container_width = width_ref ^ + } + else { + container_width = container.computed.content.max.x - container.computed.content.min.x + } + container_height := container.computed.content.max.y - container.computed.content.min.y + + // do layout calculations for the children + total_stretch_ratio : f32 = 0.0 + size_req_children : f32 = 0 + for child := container.first; child != nil; child = child.next + { + using child.layout + scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) + if .Scale_Width_By_Height_Ratio in flags + { + size_req_children += size.min.x * container_height + continue + } + if .Fixed_Width in flags + { + size_req_children += size.min.x + continue + } + + size_req_children += size.min.x + total_stretch_ratio += anchor.ratio.x + } + + avail_flex_space := container_width - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space, container_height : f32 ) -> (space_allocated : f32) + { + using child.layout + if .Scale_Width_By_Height_Ratio in flags { + size.min.y = container_height + space_allocated = size.min.x * container_height + } + else if ! (.Fixed_Width in flags) { + potential_size := anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space + space_allocated = max(potential_size, size.min.x) + size.min.x = space_allocated + } + else { + space_allocated = size.min.x + } + space_allocated -= margins.left - margins.right + size.min.x -= margins.left - margins.right + flags |= {.Fixed_Width} + return + } + + space_used : f32 = 0.0 + switch direction{ + case .Left_To_Right: + for child := container.first; child != nil; child = child.next { + using child.layout + child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height) + anchor = range2({0, anchor.bottom}, {0, anchor.top}) + alignment = {0, alignment.y} + pos.x = space_used + space_used += child_width + child.layout.margins.left + child.layout.margins.right + } + case .Right_To_Left: + for child := container.first; child != nil; child = child.next { + using child.layout + child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height) + anchor = range2({1, anchor.bottom}, {0, anchor.top}) + alignment = {1, alignment.y} + pos.x = space_used + space_used -= child_width + child.layout.margins.left + child.layout.margins.right + } + } +} + +ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_LayoutDirection_Y, height_ref : ^f32 = nil ) +{ + container_height : f32 + if height_ref != nil { + container_height = height_ref ^ + } + else { + container_height = container.computed.content.max.y - container.computed.content.min.y + } + container_width := container.computed.content.max.x - container.computed.content.min.x + + // do layout calculations for the children + total_stretch_ratio : f32 = 0.0 + size_req_children : f32 = 0 + for child := container.first; child != nil; child = child.next + { + using child.layout + scaled_height_by_width : b32 = b32(.Scale_Height_By_Width_Ratio in flags) + if scaled_height_by_width { + size_req_children += size.min.y * container_width + continue + } + if .Fixed_Height in flags + { + size_req_children += size.min.y + continue + } + + size_req_children += size.min.y + total_stretch_ratio += anchor.ratio.y + } + + avail_flex_space := container_height - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space, container_width : f32 ) -> (space_allocated : f32) + { + using child.layout + if .Scale_Height_By_Width_Ratio in flags { + size.min.x = container_width + space_allocated = size.min.y * container_width + } + if ! (.Fixed_Height in flags) { + potential_size := (anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space) + space_allocated = max(potential_size, size.min.y) + size.min.y = space_allocated + } + else { + space_allocated = size.min.y + } + space_allocated -= margins.top - margins.bottom + size.min.y -= margins.top - margins.bottom + flags |= {.Fixed_Height} + return + } + + space_used : f32 = 0 + switch direction + { + case .Top_To_Bottom: + for child := container.first; child != nil; child = child.next { + using child.layout + child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width) + anchor = range2({anchor.left, 1}, {anchor.right, 0}) + alignment = {alignment.x, 1} + pos.y = space_used + space_used -= child_height - child.layout.margins.top - child.layout.margins.bottom + } + case .Bottom_To_Top: + for child := container.first; child != nil; child = child.next { + using child.layout + child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width) + anchor = range2({anchor.left,0}, {anchor.right, 0}) + alignment = {alignment.x, 0} + pos.y = space_used + space_used += child_height - child.layout.margins.top - child.layout.margins.bottom + } + } +}