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.
This commit is contained in:
2025-07-07 02:00:57 -04:00
parent 87d5cda2c0
commit 6d780482c7
8 changed files with 265 additions and 203 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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
{

View File

@ -0,0 +1,8 @@
package sectr
Host_API :: struct {
// request_virtual_memory: HostAPI_RequestVirtualMemory,
// request_virtual_mapped_io: HostAPI_RequestVirtaulMappedIO,
// enqueue_job: ,
}

View File

@ -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) {
}

View File

@ -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

View File

@ -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
}
}
}