job refactor to move futexes out of platform layer

This commit is contained in:
jacob 2025-09-11 08:49:06 -05:00
parent 0518513478
commit 1abf58d45b
42 changed files with 1076 additions and 1602 deletions

View File

@ -9,6 +9,10 @@ set program_build_cmd=meta.exe %*
set meta_build_cmd=cl.exe ../src/meta/meta.c -Od -Z7 -nologo -diagnostics:column -WX -link -DEBUG:FULL -INCREMENTAL:NO
set meta_rebuild_code=1317212284
if "%force_meta_build%"=="1" (
if exist meta.exe del meta.exe
)
::- Meta build
:meta_build
if not exist meta.exe (
@ -22,7 +26,7 @@ if not exist meta.exe (
)
::- Program build
if not "%nobuild%"=="1" (
if not "%no_program_build%"=="1" (
echo ======== Build ========
%program_build_cmd%
set "rc=!errorlevel!"

View File

@ -16,15 +16,13 @@ Struct(AppArgList)
};
////////////////////////////////
//~ State
//~ State types
Struct(SharedAppState)
{
Arena *arena;
String write_path;
};
extern SharedAppState shared_app_state;
} extern shared_app_state;
////////////////////////////////
//~ App functions

View File

@ -38,7 +38,7 @@ Struct(AC_Store)
};
////////////////////////////////
//~ State
//~ State types
#define AC_MaxAssets 1024
#define AC_AssetLookupTableCapacity (AC_MaxAssets * 4)
@ -58,9 +58,7 @@ Struct(AC_SharedState)
u64 dbg_table_count;
Mutex dbg_table_mutex;
#endif
};
extern AC_SharedState AC_shared_state;
} extern AC_shared_state;
////////////////////////////////
//~ Startup

View File

@ -95,6 +95,10 @@
# error Unknown architecture
# endif
//- Cache line size
/* TODO: Just hard-code to something like 128 or 256 if Apple silicon is ever supported */
#define CachelineSize 64
//- Windows NTDDI version
/* TODO: Remove this */
#if 0
@ -139,7 +143,7 @@
#if RtcIsEnabled
# if CompilerIsMsvc
# define Assert(cond) ((cond) ? 1 : (IsRunningInDebugger() ? (*(volatile i32 *)0 = 0) : Panic(Lit(__FILE__ ":" Stringize(__LINE__) ":0: assertion failed: "#cond""))))
# define DEBUGBREAK __debugbreak
# define DEBUGBREAK __debugbreak()
# else
# define Assert(cond) ((cond) ? 1 : (__builtin_trap(), 0))
# define DEBUGBREAK __builtin_debugtrap()
@ -405,6 +409,7 @@ void __asan_unpoison_memory_region(void const volatile *add, size_t);
//- Struct
#define Struct(name) typedef struct name name; struct name
#define AlignedStruct(name, n) typedef struct name name; struct alignas(n) name
#define AlignedBlock(n) struct alignas(n)
//- Enum
#define Enum(name) typedef enum name name; enum name
@ -530,14 +535,14 @@ Struct(Atomic32) { volatile i32 _v; };
Struct(Atomic64) { volatile i64 _v; };
//- Cache-line isolated aligned atomic types
AlignedStruct(Atomic8Padded, 64) { Atomic8 v; u8 _pad[63]; };
AlignedStruct(Atomic16Padded, 64) { Atomic16 v; u8 _pad[62]; };
AlignedStruct(Atomic32Padded, 64) { Atomic32 v; u8 _pad[60]; };
AlignedStruct(Atomic64Padded, 64) { Atomic64 v; u8 _pad[56]; };
StaticAssert(sizeof(Atomic8Padded) == 64 && alignof(Atomic8Padded) == 64);
StaticAssert(sizeof(Atomic16Padded) == 64 && alignof(Atomic16Padded) == 64);
StaticAssert(sizeof(Atomic32Padded) == 64 && alignof(Atomic32Padded) == 64);
StaticAssert(sizeof(Atomic64Padded) == 64 && alignof(Atomic64Padded) == 64);
AlignedStruct(Atomic8Padded, CachelineSize) { Atomic8 v; };
AlignedStruct(Atomic16Padded, CachelineSize) { Atomic16 v; };
AlignedStruct(Atomic32Padded, CachelineSize) { Atomic32 v; };
AlignedStruct(Atomic64Padded, CachelineSize) { Atomic64 v; };
StaticAssert(alignof(Atomic8Padded) == CachelineSize && sizeof(Atomic8Padded) % CachelineSize == 0);
StaticAssert(alignof(Atomic16Padded) == CachelineSize && sizeof(Atomic16Padded) % CachelineSize == 0);
StaticAssert(alignof(Atomic32Padded) == CachelineSize && sizeof(Atomic32Padded) % CachelineSize == 0);
StaticAssert(alignof(Atomic64Padded) == CachelineSize && sizeof(Atomic64Padded) % CachelineSize == 0);
#if PlatformIsWindows && ArchIsX64
//- 8 bit atomic operations
@ -550,8 +555,8 @@ StaticAssert(sizeof(Atomic64Padded) == 64 && alignof(Atomic64Padded) == 64);
#define Atomic16Fetch(x) (x)->_v
#define Atomic16FetchSet(x, e) (i16)_InterlockedExchange16(&(x)->_v, (e))
#define Atomic16FetchTestSet(x, c, e) (i16)_InterlockedCompareExchange16(&(x)->_v, (e), (c))
#define Atomic16FetchTestXor(x, c) (i16)_InterlockedXor16(&(x)->_v, (c))
#define Atomic16FetchTestAdd(x, a) (i16)_InterlockedExchangeAdd16(&(x)->_v, (a))
#define Atomic16FetchXor(x, c) (i16)_InterlockedXor16(&(x)->_v, (c))
#define Atomic16FetchAdd(x, a) (i16)_InterlockedExchangeAdd16(&(x)->_v, (a))
//- 32 bit atomic operations
#define Atomic32Fetch(x) (x)->_v
#define Atomic32FetchSet(x, e) (i32)_InterlockedExchange((volatile long *)&(x)->_v, (e))
@ -576,14 +581,14 @@ StaticAssert(sizeof(Atomic64Padded) == 64 && alignof(Atomic64Padded) == 64);
#if LanguageIsC
Struct(TicketMutex)
{
Atomic64Padded ticket;
Atomic64Padded serving;
Atomic16Padded ticket;
Atomic16Padded serving;
};
ForceInline void LockTicketMutex(TicketMutex *tm)
{
i64 ticket = Atomic64FetchAdd(&tm->ticket.v, 1);
while (Atomic64Fetch(&tm->serving.v) != ticket)
u16 ticket = Atomic16FetchAdd(&tm->ticket.v, 1);
while (Atomic16Fetch(&tm->serving.v) != ticket)
{
_mm_pause();
}
@ -591,7 +596,7 @@ ForceInline void LockTicketMutex(TicketMutex *tm)
ForceInline void UnlockTicketMutex(TicketMutex *tm)
{
Atomic64FetchAdd(&tm->serving.v, 1);
Atomic16FetchAdd(&tm->serving.v, 1);
}
#endif
@ -703,7 +708,7 @@ Struct(ComputeShader) { Resource resource; };
#if LanguageIsC
# if PlatformIsWindows
# define FiberId() (*(volatile i16 *)__readgsqword(32))
# define FiberId() (*(volatile i16 *)__readgsqword(0x20))
# else
# error FiberId not implemented
# endif
@ -727,8 +732,11 @@ typedef ExitFuncDef(ExitFunc);
//- Core hooks
StringList GetCommandLineArgs(void);
void Echo(String msg);
b32 Panic(String msg);
b32 IsRunningInDebugger(void);
i64 TimeNs(void);
u32 GetNumHardwareThreads(void);
void TrueRand(String buffer);
void OnExit(ExitFunc *func);
void SignalExit(i32 code);

View File

@ -1,7 +1,7 @@
////////////////////////////////
//~ Arena types
#define ArenaHeaderSize 64
#define ArenaHeaderSize CachelineSize
#define ArenaBlockSize 16384
Struct(Arena)
@ -25,7 +25,7 @@ Struct(TempArena)
};
////////////////////////////////
//~ State
//~ State types
#define ScratchArenasPerCtx 2
@ -38,9 +38,7 @@ Struct(FiberArenaCtx)
Struct(SharedArenaCtx)
{
FiberArenaCtx arena_contexts[MaxFibers];
};
extern SharedArenaCtx shared_arena_ctx;
} extern shared_arena_ctx;
////////////////////////////////
//~ Arena push/pop
@ -125,7 +123,7 @@ void ShrinkArena(Arena *arena);
void SetArenaReadonly(Arena *arena);
void SetArenaReadWrite(Arena *arena);
Inline void *AlignArena(Arena *arena, u64 align)
Inline void *PushAlign(Arena *arena, u64 align)
{
Assert(!arena->readonly);
if (align > 0)

185
src/base/base_futex.c Normal file
View File

@ -0,0 +1,185 @@
SharedFutexState shared_futex_state = ZI;
////////////////////////////////
//~ Startup
void InitFutexSystem(void)
{
SharedFutexState *g = &shared_futex_state;
}
////////////////////////////////
//~ State helpers
FiberNeqFutexState *FiberNeqFutexStateFromId(i16 fiber_id)
{
return &shared_futex_state.fiber_neq_states[fiber_id];
}
////////////////////////////////
//~ Not-equal futex operations
void FutexYieldNeq(volatile void *addr, void *cmp, u8 cmp_size)
{
SharedFutexState *g = &shared_futex_state;
FutexNeqListBin *bin = &g->neq_bins[RandU64FromSeed((u64)addr) % countof(g->neq_bins)];
b32 cancel = 0;
LockTicketMutex(&bin->tm);
{
/* Now that bin is locked, check if we should cancel insertion based on current value at address */
{
union
{
volatile void *v;
Atomic8 *v8;
Atomic16 *v16;
Atomic32 *v32;
Atomic64 *v64;
} addr_atomic;
union
{
void *v;
i8 *v8;
i16 *v16;
i32 *v32;
i64 *v64;
} cmp_int;
addr_atomic.v = addr;
cmp_int.v = cmp;
switch(cmp_size)
{
default: Assert(0); cancel = 1; break; /* Invalid futex size */
case 1: cancel = Atomic8Fetch(addr_atomic.v8) != *cmp_int.v8; break;
case 2: cancel = Atomic16Fetch(addr_atomic.v16) != *cmp_int.v16; break;
case 4: cancel = Atomic32Fetch(addr_atomic.v32) != *cmp_int.v32; break;
case 8: cancel = Atomic64Fetch(addr_atomic.v64) != *cmp_int.v64; break;
}
}
if (!cancel)
{
/* Grab futex list from address */
FutexNeqList *list = 0;
{
list = bin->first;
for (; list; list = list->next)
{
if (list->addr == addr) break;
}
if (!list)
{
if (bin->first_free)
{
list = bin->first_free;
bin->first_free = list->next;
ZeroStruct(list);
}
else
{
Arena *perm = PermArena();
PushAlign(perm, CachelineSize);
list = PushStruct(perm, FutexNeqList);
PushAlign(perm, CachelineSize);
list->addr = addr;
}
list->next = bin->first;
bin->first = list;
}
}
/* Insert */
{
i16 fiber_id = FiberId();
FiberNeqFutexState *f = FiberNeqFutexStateFromId(fiber_id);
f->next = list->first;
list->first = fiber_id;
}
}
}
UnlockTicketMutex(&bin->tm);
/* Suspend */
if (!cancel)
{
SuspendFiber();
}
}
void FutexWakeNeq(void *addr)
{
SharedFutexState *g = &shared_futex_state;
FutexNeqListBin *bin = &g->neq_bins[RandU64FromSeed((u64)addr) % countof(g->neq_bins)];
/* Pull waiting ids */
i16 first_id = 0;
{
LockTicketMutex(&bin->tm);
{
FutexNeqList *list = bin->first;
for (; list; list = list->next)
{
if (list->addr == addr) break;
}
if (list)
{
first_id = list->first;
/* Free futex list */
{
FutexNeqList *prev = list->prev;
FutexNeqList *next = list->next;
if (prev)
{
prev->next = next;
}
else
{
bin->first = next;
}
if (next)
{
next->prev = prev;
}
list->next = bin->first_free;
bin->first_free = list;
}
}
}
UnlockTicketMutex(&bin->tm);
}
/* Resume fibers */
if (first_id != 0)
{
TempArena scratch = BeginScratchNoConflict();
i16 *ids = PushDry(scratch.arena, i16);
i16 ids_count = 0;
{
i16 id = first_id;
while (id != 0)
{
FiberNeqFutexState *f = FiberNeqFutexStateFromId(id);
*PushStructNoZero(scratch.arena, i16) = id;
++ids_count;
id = FiberNeqFutexStateFromId(id)->next;
}
}
ResumeFibers(ids_count, ids);
EndScratch(scratch);
}
}
////////////////////////////////
//~ Greater-than-or-equal futex operations
void FutexYieldGte(volatile void *addr, void *cmp, u8 cmp_size)
{
/* TODO: Actually implement this. Just emulating via neq for now. */
FutexYieldNeq(addr, cmp, cmp_size);
}
void FutexWakeGte(void *addr)
{
/* TODO: Actually implement this. Just emulating via neq for now. */
FutexWakeNeq(addr);
}

67
src/base/base_futex.h Normal file
View File

@ -0,0 +1,67 @@
////////////////////////////////
//~ Neq futex types
Struct(FutexNeqList)
{
FutexNeqList *prev;
FutexNeqList *next;
volatile void *addr;
i16 first;
};
Struct(FutexNeqListBin)
{
TicketMutex tm;
FutexNeqList *first;
FutexNeqList *first_free;
};
////////////////////////////////
//~ State types
#define FutexNeqBinsCount 16384
#define FutexGteBinsCount 16384
AlignedStruct(FiberNeqFutexState, CachelineSize)
{
i16 next;
};
Struct(SharedFutexState)
{
FiberNeqFutexState fiber_neq_states[MaxFibers];
FutexNeqListBin neq_bins[FutexNeqBinsCount];
} extern shared_futex_state;
////////////////////////////////
//~ Startup
void InitFutexSystem(void);
////////////////////////////////
//~ State helpers
FiberNeqFutexState *FiberNeqFutexStateFromId(i16 fiber_id);
////////////////////////////////
//~ Not-equal futex operations
/* Similar to Win32 WaitOnAddress & WakeByAddressAll
* i.e. - Suprious wait until value at address != cmp */
void FutexYieldNeq(volatile void *addr, void *cmp, u8 cmp_size);
void FutexWakeNeq(void *addr);
////////////////////////////////
//~ Greater-than-or-equal futex operations
/* Similar to Win32 WaitOnAddress & WakeByAddressAll
* i.e. - Spurious wait until monotonically increasing value at address >= cmp (used for fences)
*
* NOTE: This API is offered for fence-like semantics, where waiters only want to
* wake when the futex progresses past the specified target value, rather than
* wake every time the futex is modified.
**/
void FutexYieldGte(volatile void *addr, void *cmp, u8 cmp_size);
void FutexWakeGte(void *addr);

View File

@ -8,6 +8,7 @@
# include "base_intrinsics.h"
# include "base_memory.h"
# include "base_arena.h"
# include "base_futex.h"
# include "base_snc.h"
# include "base_job.h"
# include "base_uid.h"
@ -29,6 +30,7 @@
#if LanguageIsC
# include "base_memory.c"
# include "base_arena.c"
# include "base_futex.c"
# include "base_snc.c"
# include "base_uid.c"
# include "base_string.c"

View File

@ -1,99 +1,91 @@
////////////////////////////////
//~ Job queue types
//~ Opaque types
/* Work pools contain their own worker threads with their own thread priorities
Struct(Job);
////////////////////////////////
//~ Job pool types
/* Job pools contain worker threads with their own thread priorities
* and affinities based on the intended context of the pool. */
Enum(JobPool)
{
JobPool_Inherit = -1,
/* Contains critical-priority worker threads affinitized over the entire CPU.
/* Contains worker threads affinitized over the entire CPU.
* Meant to take on high-bandwidth temporary work (e.g. loading a level). */
JobPool_Hyper = 0,
JobPool_Hyper = 0, /* High-priority */
/* Contains low-priority worker threads affinitized over the entire CPU.
* Meant to take on blocking work that higher-priority workers can continue doing actual work. */
JobPool_Blocking = 1,
/* Contains worker threads affinitized over the entire CPU.
* Meant to take on blocking work so that affinitized workers workers can continue doing actual work. */
JobPool_Blocking = 1, /* Normal priority */
/* Contains low-priority worker threads affinitized to cores that don't interfere with workers in specialized pools.
* Meant to take on asynchronous work from higher priority pools. */
JobPool_Background = 2,
/* Contains worker threads affinitized to cores that don't interfere with workers in specialized pools.
* Meant to consume asynchronous work from higher priority pools. */
JobPool_Background = 2, /* Normal priority */
/* Contains high-priority worker threads affinitized to cores that don't interfere with workers in other specialized pools.
* These pools are meant to only have work pushed onto them from jobs within the same pool (e.g. pushing 100 rendering
* jobs will not interfere with cores running workers on the Sim pool as long as the jobs are pushed onto the User pool). */
JobPool_Audio = 3,
JobPool_User = 4,
JobPool_Sim = 5,
/* Contains worker threads affinitized to cores that don't interfere with workers in other specialized pools.
* These pools are meant to only have work pushed onto them from jobs within the same pool (e.g. 100 jobs pushed onto the
* User pool will not interfere with cores running workers on the Sim pool). */
JobPool_Audio = 3, /* Critical priority */
JobPool_User = 4, /* Above-normal priority */
JobPool_Sim = 5, /* Above-normal priority */
JobPool_Count
};
/* Job execution order within a pool is based on priority. */
Enum(JobPriority)
{
JobPriority_Inherit = -1,
JobPriority_High = 0,
JobPriority_Normal = 1,
JobPriority_Low = 2,
////////////////////////////////
//~ Job types
JobPriority_Count
Struct(JobCounter)
{
Fence jobs_completed_fence;
Atomic64Padded num_jobs_dispatched;
};
typedef void JobFunc(void *, i32);
Struct(Job)
{
Job *next;
Arena *arena;
JobFunc *func;
JobPool pool;
i32 count;
JobCounter *counter;
void *sig;
Atomic64Padded num_tasks_completed;
};
////////////////////////////////
//~ @hookdecl Init
void InitJobWorkers(void);
void InitJobSystem(void);
////////////////////////////////
//~ @hookdecl Futex
//~ @hookdecl Fiber suspend/resume operations
/* Futex-like wait & wake */
void FutexYield(volatile void *addr, void *cmp, u32 size, i64 timeout_ns);
void FutexWake(void *addr, i32 count);
void SuspendFiber(void);
void ResumeFibers(i16 fiber_ids_count, i16 *fiber_ids);
////////////////////////////////
//~ @hookdecl Job operations
typedef void GenericJobFunc(void *, i32);
Struct(GenericJobDesc)
{
Arena *arena;
void *sig;
GenericJobFunc *func;
i32 count;
JobPool pool;
JobPriority priority;
Counter *counter;
/* Internal */
Atomic32 num_completed;
GenericJobDesc *next_free;
};
Struct(JobDescParams)
{
i32 count;
JobPool pool;
JobPriority priority;
Counter *counter;
};
//~ @hookdecl Job declaration operations
#define EmptySig { i32 _; }
#define PushJobDesc(job, ...) (job##_Desc *)PushJobDesc_(sizeof(job##_Sig), alignof(job##_Sig), job##_Generic, (JobDescParams) { .count = 1, .pool = JobPool_Inherit, .priority = JobPriority_Inherit, .counter = 0, __VA_ARGS__ })
GenericJobDesc *PushJobDesc_(u64 sig_size, u64 sig_align, GenericJobFunc *func, JobDescParams params);
#define JobDecl(job, sigdef) \
typedef struct job##_Sig sigdef job##_Sig; \
Struct(job##_Desc) { Arena *arena; job##_Sig *sig; GenericJobFunc *func; i32 count; JobPool pool; JobPriority priority; Counter *counter; }; \
Struct(job##_Desc) { JobFunc *func; JobPool pool; u32 count; JobCounter *counter; job##_Sig sig; }; \
void job(job##_Sig *, i32); \
inline void job##_Generic(void *sig, i32 id) { job((job##_Sig *)sig, id); } \
StaticAssert(1)
#define JobDef(job, sig_arg, id_arg) void job(job##_Sig *sig_arg, i32 id_arg)
////////////////////////////////
//~ @hookdecl Job dispatch operations
/* RunJob example usage:
*
* This example pushes a single 'LoadTextureJob' onto the background job
@ -101,24 +93,23 @@ GenericJobDesc *PushJobDesc_(u64 sig_size, u64 sig_align, GenericJobFunc *func,
* and then immediately yielded on in this example, effectively making
* the operation synchronous as the caller will block until the job completes:
* {
* Counter counter = {0};
* RunJob(1, LoadTextureJob, JobPool_Background, JobPriority_Low, &counter, .resource = sprite);
* YieldOnCounter(&counter);
* JobCounter counter = {0};
* RunJob(LoadTextureJob, .pool = JobPool_Background, .counter = &counter, .sig = { .resource = sprite });
* YieldOnJobs(&counter);
* }
*
*/
#define RunJob(_count, job, _pool, _priority, _counter, ...) \
#define RunJob(name, ...) \
do { \
job##_Desc *__job_desc = (job##_Desc *)PushJobDesc_(sizeof(job##_Sig), alignof(job##_Sig), job##_Generic, (JobDescParams) { .count = _count, .pool = _pool, .priority = _priority, .counter = _counter,}); \
*__job_desc->sig = (job##_Sig) { __VA_ARGS__ }; \
RunJobEx((GenericJobDesc *)__job_desc); \
name##_Desc __desc = { .count = 1, .pool = JobPool_Inherit, .func = name, __VA_ARGS__ }; \
Job *__job = OpenJob(__desc.func, __desc.pool); \
__job->count = __desc.count; \
__job->counter = __desc.counter; \
__job->sig = PushStructNoZero(__job->arena, name##_Sig); \
CopyBytes(__job->sig, &__desc.sig, sizeof(__desc.sig)); \
CloseJob(__job); \
} while (0)
void RunJobEx(GenericJobDesc *desc);
////////////////////////////////
//~ @hookdecl Helpers
i64 TimeNs(void);
u32 GetLogicalProcessorCount(void);
i64 GetCurrentSchedulerPeriodNs(void);
Job *OpenJob(JobFunc *func, JobPool pool_kind);
void CloseJob(Job *job);
#define YieldOnJobs(counter) (YieldOnFence(&(counter)->jobs_completed_fence, Atomic64Fetch(&(counter)->num_jobs_dispatched.v)))

View File

@ -3,7 +3,7 @@ SharedResourceState shared_resource_state = ZI;
////////////////////////////////
//~ Startup
void InitBaseResources(u64 archive_strings_count, String *archive_strings)
void InitResourceSystem(u64 archive_strings_count, String *archive_strings)
{
SharedResourceState *g = &shared_resource_state;
Arena *perm = PermArena();

View File

@ -18,7 +18,7 @@ Struct(ResourceEntryBin)
};
////////////////////////////////
//~ State
//~ State types
#define NumResourceEntryBins 4096
@ -28,14 +28,12 @@ Struct(SharedResourceState)
ResourceEntry *last_entry;
u64 entries_count;
ResourceEntryBin bins[NumResourceEntryBins];
};
extern SharedResourceState shared_resource_state;
} extern shared_resource_state;
////////////////////////////////
//~ Startup
void InitBaseResources(u64 archive_strings_count, String *archive_strings);
void InitResourceSystem(u64 archive_strings_count, String *archive_strings);
////////////////////////////////
//~ Resource operations

View File

@ -49,7 +49,7 @@ Lock LockSpinE(Mutex *m, i32 spin)
}
else
{
FutexYield(&m->v, &v, 4, I64Max);
FutexYieldNeq(&m->v, &v, 4);
spin_cnt = 0;
}
}
@ -96,7 +96,7 @@ Lock LockSpinS(Mutex *m, i32 spin)
}
else
{
FutexYield(&m->v, &v, 4, I64Max);
FutexYieldNeq(&m->v, &v, 4);
spin_cnt = 0;
}
}
@ -131,7 +131,7 @@ void Unlock(Lock *l)
{
Atomic32FetchAdd(&m->v, -1);
}
FutexWake(&m->v, I32Max);
FutexWakeNeq(&m->v);
ZeroStruct(l);
}
@ -139,11 +139,6 @@ void Unlock(Lock *l)
//~ Condition variable
void YieldOnCv(Cv *cv, Lock *l)
{
YieldOnCvTime(cv, l, I64Max);
}
void YieldOnCvTime(Cv *cv, Lock *l, i64 timeout_ns)
{
u64 old_wake_gen = Atomic64Fetch(&cv->wake_gen);
Mutex *mutex = l->mutex;
@ -151,7 +146,7 @@ void YieldOnCvTime(Cv *cv, Lock *l, i64 timeout_ns)
{
Unlock(l);
{
FutexYield(&cv->wake_gen, &old_wake_gen, sizeof(old_wake_gen), timeout_ns);
FutexYieldNeq(&cv->wake_gen, &old_wake_gen, sizeof(old_wake_gen));
}
if (exclusive)
{
@ -164,10 +159,10 @@ void YieldOnCvTime(Cv *cv, Lock *l, i64 timeout_ns)
}
}
void SignalCv(Cv *cv, i32 count)
void SignalCv(Cv *cv)
{
Atomic64FetchAdd(&cv->wake_gen, 1);
FutexWake(&cv->wake_gen, count);
FutexWakeNeq(&cv->wake_gen);
}
////////////////////////////////
@ -184,7 +179,7 @@ void AddCounter(Counter *counter, i64 x)
i64 new_v = old_v + x;
if (old_v > 0 && new_v <= 0)
{
FutexWake(&counter->v, I32Max);
FutexWakeNeq(&counter->v);
}
}
@ -193,7 +188,42 @@ void YieldOnCounter(Counter *counter)
i64 v = Atomic64Fetch(&counter->v);
while (v > 0)
{
FutexYield(&counter->v, &v, sizeof(v), I64Max);
FutexYieldNeq(&counter->v, &v, sizeof(v));
v = Atomic64Fetch(&counter->v);
}
}
////////////////////////////////
//~ Fence
u64 FetchFence(Fence *fence)
{
return Atomic64Fetch(&fence->v.v);
}
u64 FetchSetFence(Fence *fence, u64 x)
{
u64 fetch = Atomic64FetchSet(&fence->v.v, x);
if (x > fetch)
{
FutexWakeGte(&fence->v.v);
}
return fetch;
}
u64 FetchAddFence(Fence *fence, u64 x)
{
u64 fetch = Atomic64FetchAdd(&fence->v.v, x);
FutexWakeGte(&fence->v.v);
return fetch;
}
void YieldOnFence(Fence *fence, u64 target)
{
u64 v = Atomic64Fetch(&fence->v.v);
while (v < target)
{
FutexYieldGte(&fence->v.v, &v, sizeof(v));
v = Atomic64Fetch(&fence->v.v);
}
}

View File

@ -3,7 +3,7 @@
#define DefaultMutexSpin 4000
AlignedStruct(Mutex, 64)
AlignedStruct(Mutex, CachelineSize)
{
/* Bit 31 = Exclusive lock is held
* Bit 30 = Exclusive lock is pending
@ -13,13 +13,9 @@ AlignedStruct(Mutex, 64)
#if RtcIsEnabled
Atomic32 exclusive_fiber_id;
u8 _pad[56];
#else
u8 _pad[60];
#endif
};
StaticAssert(sizeof(Mutex) == 64); /* Padding validation */
StaticAssert(alignof(Mutex) == 64); /* Prevent false sharing */
StaticAssert(alignof(Mutex) == CachelineSize && sizeof(Mutex) % CachelineSize == 0);
Struct(Lock)
{
@ -30,24 +26,28 @@ Struct(Lock)
////////////////////////////////
//~ Condition variable types
AlignedStruct(Cv, 64)
AlignedStruct(Cv, CachelineSize)
{
Atomic64 wake_gen;
u8 _pad[56];
};
StaticAssert(sizeof(Cv) == 64); /* Padding validation */
StaticAssert(alignof(Cv) == 64); /* Prevent false sharing */
StaticAssert(alignof(Cv) == CachelineSize && sizeof(Cv) % CachelineSize == 0);
////////////////////////////////
//~ Counter types
AlignedStruct(Counter, 64)
AlignedStruct(Counter, CachelineSize)
{
Atomic64 v;
u8 _pad[56];
};
StaticAssert(sizeof(Counter) == 64); /* Padding validation */
StaticAssert(alignof(Counter) == 64); /* Prevent false sharing */
StaticAssert(alignof(Counter) == CachelineSize && sizeof(Counter) % CachelineSize == 0);
////////////////////////////////
//~ Fence types
Struct(Fence)
{
Atomic64Padded v;
};
////////////////////////////////
//~ Mutex operations
@ -73,8 +73,7 @@ void Unlock(Lock *lock);
//~ Condition variable operations
void YieldOnCv(Cv *cv, Lock *lock);
void YieldOnCvTime(Cv *cv, Lock *l, i64 timeout_ns);
void SignalCv(Cv *cv, i32 count);
void SignalCv(Cv *cv);
////////////////////////////////
//~ Counter operations
@ -82,3 +81,12 @@ void SignalCv(Cv *cv, i32 count);
i64 ValueFromCounter(Counter *counter);
void AddCounter(Counter *counter, i64 x);
void YieldOnCounter(Counter *counter);
////////////////////////////////
//~ Fence operations
u64 FetchFence(Fence *fence);
u64 FetchSetFence(Fence *fence, u64 x);
u64 FetchAddFence(Fence *fence, u64 x);
void YieldOnFence(Fence *fence, u64 target);

View File

@ -44,6 +44,15 @@ StringList GetCommandLineArgs(void)
return result;
}
void Echo(String msg)
{
HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
}
}
b32 Panic(String msg)
{
char msg_cstr[4096];
@ -73,6 +82,20 @@ b32 IsRunningInDebugger(void)
return IsDebuggerPresent();
}
i64 TimeNs(void)
{
struct W32_SharedState *g = &W32_shared_state;
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
i64 result = (qpc.QuadPart - g->timer_start_qpc) * g->ns_per_qpc;
return result;
}
u32 GetNumHardwareThreads(void)
{
return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
}
void TrueRand(String buffer)
{
BCryptGenRandom(BCRYPT_RNG_ALG_HANDLE, (u8 *)buffer.text, buffer.len, 0);
@ -216,20 +239,35 @@ i32 W32_Main(void)
g->exit_begin_event = CreateEventW(0, 1, 0, 0);
g->exit_end_event = CreateEventW(0, 1, 0, 0);
/* Query performance frequency */
{
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
g->ns_per_qpc = 1000000000 / qpf.QuadPart;
}
{
LARGE_INTEGER qpc;
QueryPerformanceCounter(&qpc);
g->timer_start_qpc = qpc.QuadPart;
}
g->main_thread_id = GetCurrentThreadId();
SetThreadDescription(GetCurrentThread(), L"Main thread");
/* Query system info */
GetSystemInfo(&g->info);
/* Startup workers */
InitJobWorkers();
/* Init job system */
InitJobSystem();
/* Init futex system */
InitFutexSystem();
/* Init resources */
{
W32_FindEmbeddedDataCtx ctx = ZI;
EnumResourceNamesW(0, RT_RCDATA, &W32_FindEmbeddedRcData, (LONG_PTR)&ctx);
InitBaseResources(ctx.embedded_strings_count, ctx.embedded_strings);
InitResourceSystem(ctx.embedded_strings_count, ctx.embedded_strings);
}
//- App startup
@ -237,7 +275,7 @@ i32 W32_Main(void)
/* Startup layers */
if (!Atomic32Fetch(&g->panicking))
{
RunJob(1, W32_StartupLayersJob, JobPool_Hyper, JobPriority_High, 0, 0);
RunJob(W32_StartupLayersJob);
}
/* Wait for startup end or panic */
@ -265,7 +303,7 @@ i32 W32_Main(void)
/* Run exit callbacks job */
if (!Atomic32Fetch(&g->panicking))
{
RunJob(1, W32_ShutdownLayersJob, JobPool_Hyper, JobPriority_High, 0, 0);
RunJob(W32_ShutdownLayersJob);
}
/* Wait for exit end or panic */
@ -293,19 +331,13 @@ i32 W32_Main(void)
#if CrtlibIsEnabled
# if IsConsoleApp
int main(char **argc, int argv)
int main(UNUSED char **argc, UNUSED int argv)
{
LAX argc;
LAX argv;
return W32_Main();
}
# else
int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, _In_ LPWSTR cmdline_wstr, _In_ int show_code)
int CALLBACK wWinMain(UNUSED _In_ HINSTANCE instance, UNUSED _In_opt_ HINSTANCE prev_instance, UNUSED _In_ LPWSTR cmdline_wstr, UNUSED _In_ int show_code)
{
LAX instance;
LAX prev_instance;
LAX cmdline_wstr;
LAX show_code;
return W32_Main();
}
# endif /* IsConsoleApp */

View File

@ -20,7 +20,7 @@ Struct(W32_FindEmbeddedDataCtx)
};
////////////////////////////////
//~ State
//~ State types
#define W32_MaxOnExitFuncs 4096
@ -31,6 +31,9 @@ Struct(W32_SharedState)
Atomic32 shutdown;
Atomic32 exit_code;
i64 timer_start_qpc;
i64 ns_per_qpc;
//- Application control flow
Atomic32 panicking;
wchar_t panic_wstr[4096];
@ -42,9 +45,7 @@ Struct(W32_SharedState)
//- Exit funcs
Atomic32 num_exit_funcs;
ExitFunc *exit_funcs[W32_MaxOnExitFuncs];
};
extern W32_SharedState W32_shared_state;
} extern W32_shared_state;
////////////////////////////////
//~ Embedded data initialization

File diff suppressed because it is too large Load Diff

View File

@ -32,42 +32,14 @@ typedef W32_ThreadDef(W32_ThreadFunc, data);
Struct(W32_Thread)
{
String thread_name;
W32_ThreadFunc *entry_point;
void *thread_data;
char thread_name_cstr[256];
wchar_t thread_name_wstr[256];
void *thread_udata;
i32 profiler_group;
W32_Thread *next;
W32_Thread *prev;
HANDLE handle;
};
////////////////////////////////
//~ Wait list types
AlignedStruct(W32_WaitList, 64)
{
u64 value;
i16 first_waiter;
i16 last_waiter;
i32 num_waiters;
W32_WaitList *next_in_bin;
W32_WaitList *prev_in_bin;
u8 pad[32];
};
StaticAssert(alignof(W32_WaitList) == 64); /* Avoid false sharing */
AlignedStruct(W32_WaitBin, 64)
{
W32_WaitList *first_wait_list;
W32_WaitList *last_wait_list;
W32_WaitList *first_free_wait_list;
TicketMutex lock;
};
StaticAssert(alignof(W32_WaitBin) == 64); /* Avoid false sharing */
////////////////////////////////
//~ Fiber types
@ -76,211 +48,140 @@ StaticAssert(alignof(W32_WaitBin) == 64); /* Avoid false sharing */
#define W32_FiberNameSuffixCstr "]"
#define W32_FiberNameMaxSize 64
//- Yield param
Enum(W32_YieldKind)
{
W32_YieldKind_None,
W32_YieldKind_Done,
W32_YieldKind_Wait,
W32_YieldKind_Count
};
Struct(W32_YieldParam)
{
W32_YieldKind kind;
union
{
struct
{
volatile void *addr;
void *cmp;
u32 size;
i64 timeout_ns;
} wait;
};
};
//- Fiber
AlignedStruct(W32_Fiber, 64)
AlignedStruct(W32_Fiber, CachelineSize)
{
/* ---------------------------------------------------- */
void *addr; /* 08 bytes */
/* ---------------------------------------------------- */
char *name_cstr; /* 08 bytes */
/* ---------------------------------------------------- */
Atomic32 wake_lock; /* 04 bytes (4 byte alignment) */
Atomic8 is_suspending; /* 01 bytes */
u8 _pad0[1]; /* 01 bytes (padding) */
i16 id; /* 02 bytes */
i16 pool; /* 02 bytes */
i16 parent_id; /* 02 bytes */
/* ---------------------------------------------------- */
u64 wait_addr; /* 08 bytes */
struct W32_Task *task; /* 08 bytes */
/* ---------------------------------------------------- */
u64 wait_time; /* 08 bytes */
/* ---------------------------------------------------- */
i16 next_addr_waiter; /* 02 bytes */
i16 prev_addr_waiter; /* 02 bytes */
i16 next_time_waiter; /* 02 bytes */
i16 prev_time_waiter; /* 02 bytes */
/* ---------------------------------------------------- */
GenericJobDesc *job_desc; /* 08 bytes */
/* ---------------------------------------------------- */
u8 _pad0[8]; /* 08 bytes (padding) */
/* ---------------------------------------------------- */
/* -------------------- Cache line -------------------- */
/* ---------------------------------------------------- */
i32 job_id; /* 04 bytes */
i16 job_pool; /* 02 bytes */
i16 job_priority; /* 02 bytes */
/* ---------------------------------------------------- */
W32_YieldParam *yield_param; /* 08 bytes */
/* ---------------------------------------------------- */
u8 _pad1[48]; /* 48 bytes (padding) */
#if 0
u8 _pad1[32]; /* 32 bytes (padding) */
#endif
};
StaticAssert(sizeof(W32_Fiber) == 128); /* Padding validation (increase if necessary) */
StaticAssert(alignof(W32_Fiber) == 64); /* Verify alignment to avoid false sharing */
StaticAssert(offsetof(W32_Fiber, wake_lock) % 4 == 0); /* Atomic must be aligned */
StaticAssert(alignof(W32_Fiber) == CachelineSize && sizeof(W32_Fiber) % CachelineSize == 0); /* False sharing validation */
StaticAssert(offsetof(W32_Fiber, is_suspending) % 4 == 0); /* Atomic alignment validation */
////////////////////////////////
//~ Job queue types
//~ Job pool types
//- Worker ctx
AlignedStruct(W32_WorkerCtx, 64)
AlignedStruct(W32_WorkerCtx, CachelineSize)
{
JobPool pool_kind;
i32 id;
};
//- Job info
Struct(W32_JobInfo)
//- Task
Struct(W32_Task)
{
GenericJobDesc *desc;
i32 count;
i32 num_dispatched;
i16 fiber_id; /* If the job is being resumed from a yield */
W32_JobInfo *next;
W32_Task *next;
Job *job;
u32 task_id;
i16 fiber_id;
};
//- Job queue
AlignedStruct(W32_JobQueue, 64)
Struct(W32_TaskList)
{
TicketMutex lock;
Arena *arena;
W32_JobInfo *first;
W32_JobInfo *last;
W32_JobInfo *first_free;
W32_Task *first;
W32_Task *last;
u64 count;
};
//- Job pool
AlignedStruct(W32_JobPool, 64)
AlignedStruct(W32_JobPool, CachelineSize)
{
/* Jobs */
W32_JobQueue job_queues[JobPriority_Count];
TicketMutex free_fibers_tm;
i16 first_free_fiber_id;
/* Workers */
Atomic32Padded workers_shutdown;
Atomic64Padded num_jobs_in_queue;
TicketMutex workers_wake_tm;
JobPool kind;
i32 num_worker_threads;
i32 thread_priority;
u64 thread_affinity_mask;
b32 thread_is_audio;
Arena *worker_threads_arena;
W32_Thread **worker_threads;
W32_WorkerCtx *worker_contexts;
/* Tasks */
AlignedBlock(CachelineSize)
{
TicketMutex tasks_tm;
Atomic64Padded tasks_count;
W32_Task *first_task;
W32_Task *last_task;
};
/* TODO: Make lists wait-free */
/* Free fibers */
AlignedBlock(CachelineSize)
{
TicketMutex free_fibers_tm;
i16 first_free_fiber_id;
};
/* Free jobs */
AlignedBlock(CachelineSize)
{
TicketMutex free_jobs_tm;
Job *first_free_job;
};
/* Free tasks */
AlignedBlock(CachelineSize)
{
TicketMutex free_tasks_tm;
W32_Task *first_free_task;
};
};
////////////////////////////////
//~ State
//~ State types
/* Assume scheduler cycle is 20hz at start to be conservative */
#define W32_DefaultSchedulerPeriodNs 50000000
#define W32_NumRollingSchedulerPeriods 1000
/* Arbitrary threshold for determining when to use a looped WakeByAddressSingle vs a WakeByAddressAll to wake parked workers */
#define W32_WakeAllWorkersThreshold 8
#define W32_NumWaitAddrBins 16384
#define W32_NumWaitTimeBins 1024
/* Arbitrary threshold for determining when to fall back from a looped WakeByAddressSingle to WakeByAddressAll */
#define W32_WakeAllThreshold 16
Struct(W32_SharedJobCtx)
Struct(W32_SharedJobState)
{
i64 timer_start_qpc;
i64 ns_per_qpc;
Atomic32 shutdown;
//- Worker thread pool
TicketMutex threads_tm;
Arena *threads_arena;
W32_Thread *first_thread;
W32_Thread *last_thread;
W32_Thread *first_free_thread;
//- Scheduler
Atomic64Padded current_scheduler_cycle;
Atomic64Padded current_scheduler_cycle_period_ns;
//- Fibers
AlignedBlock(CachelineSize)
{
TicketMutex fibers_tm;
i16 num_fibers;
Arena *fiber_names_arena;
W32_Fiber fibers[MaxFibers];
//- Job descs
Arena *job_descs_arena;
TicketMutex job_descs_tm;
GenericJobDesc *first_free_job_desc;
//- Wait lists
Atomic64Padded waiter_wake_gen;
TicketMutex wait_lists_arena_tm;
Arena *wait_lists_arena;
//- Wait tables
W32_WaitBin wait_addr_bins[W32_NumWaitAddrBins];
W32_WaitBin wait_time_bins[W32_NumWaitTimeBins];
//- Job pools
W32_JobPool job_pools[JobPool_Count];
Arena *fiber_names_arena;
};
extern W32_SharedJobCtx W32_shared_job_ctx;
//- Job pools
AlignedBlock(CachelineSize)
{
W32_JobPool job_pools[JobPool_Count];
};
} extern W32_shared_job_state;
////////////////////////////////
//~ Startup
void StartupJobs(void);
////////////////////////////////
//~ Shutdown
void ShutdownJobs(void);
////////////////////////////////
//~ Thread operations
DWORD WINAPI W32_Win32ThreadProc(LPVOID vt);
W32_Thread *W32_AcquireThread(W32_ThreadFunc *entry_point, void *thread_data, String thread_name, i32 profiler_group);
b32 W32_TryReleaseThread(W32_Thread *thread, f32 timeout_seconds);
void W32_WaitReleaseThread(W32_Thread *thread);
W32_Thread *W32_StartThread(W32_ThreadFunc *entry_point, void *thread_udata, String thread_name, i32 profiler_group);
b32 W32_TryEndThread(W32_Thread *thread, f32 timeout_seconds);
void W32_WaitEndThread(W32_Thread *thread);
////////////////////////////////
//~ Wait list operations
//~ Pool operations
W32_WaitBin *W32_WaitBinFromAddr(u64 addr);
W32_WaitBin *W32_WaitBinFromTime(u64 time);
void W32_WakeLockedFibers(i32 num_fibers, W32_Fiber **fibers);
void W32_WakeByAddress(void *addr, i32 count);
void W32_WakeByTime(u64 time);
W32_JobPool *W32_JobPoolFromKind(JobPool pool_kind);
////////////////////////////////
//~ Fiber operations
@ -288,20 +189,15 @@ void W32_WakeByTime(u64 time);
W32_Fiber *W32_AcquireFiber(W32_JobPool *pool);
void W32_ReleaseFiber(W32_JobPool *pool, W32_Fiber *fiber);
ForceInline W32_Fiber *W32_FiberFromId(i16 id);
void W32_FiberResume(W32_Fiber *fiber);
void W32_SwitchToFiber(W32_Fiber *fiber);
void W32_YieldFiber(W32_Fiber *fiber, W32_Fiber *parent_fiber);
////////////////////////////////
//~ Fiber entry
void W32_FiberEntryPoint(void *id_ptr);
void W32_FiberEntryPoint(void *wi32_fiber_data);
////////////////////////////////
//~ Job worker entry
W32_ThreadDef(W32_JobWorkerEntryFunc, worker_ctx_arg);
////////////////////////////////
//~ Job scheduler entry
W32_ThreadDef(W32_JobSchedulerEntryFunc, _);

View File

@ -99,14 +99,12 @@ Struct(D_TextParams)
})
////////////////////////////////
//~ State
//~ State types
Struct(D_SharedState)
{
GPU_Resource *solid_white_texture;
};
extern D_SharedState D_shared_state;
} extern D_shared_state;
////////////////////////////////
//~ Startup

View File

@ -122,11 +122,14 @@ AC_Asset *F_LoadAsset(Resource resource, f32 point_size, b32 wait)
if (is_first_touch)
{
AC_MarkLoading(asset);
F_LoadJob_Desc *desc = PushJobDesc(F_LoadJob, .pool = JobPool_Background, .priority = JobPriority_Low);
desc->sig->asset = asset;
desc->sig->resource = resource;
desc->sig->point_size = point_size;
RunJobEx((GenericJobDesc *)desc);
{
Job *job = OpenJob(F_LoadJob, JobPool_Background);
F_LoadJob_Sig *sig = PushStruct(job->arena, F_LoadJob_Sig);
sig->asset = asset;
sig->resource = resource;
sig->point_size = point_size;
CloseJob(job);
}
if (wait)
{
AC_YieldOnAssetReady(asset);

View File

@ -145,7 +145,7 @@ Struct(GPU_D12_Swapchain)
};
////////////////////////////////
//~ State
//~ State types
Struct(GPU_D12_FiberState)
{
@ -156,7 +156,7 @@ Struct(GPU_D12_FiberState)
Struct(GPU_D12_SharedState)
{
i32 _;
};
} extern GPU_D12_shared_state;
////////////////////////////////
//~ State operations

View File

@ -62,16 +62,10 @@
////////////////////////////////
//~ Util
/* TODO: Move this to OS layer */
void Echo(String msg)
{
OS_Echo(msg);
}
void EchoLine(String msg)
{
OS_Echo(msg);
OS_Echo(Lit("\n"));
Echo(msg);
Echo(Lit("\n"));
}
Struct(LineCol)
@ -535,9 +529,12 @@ JobDef(StepJob, sig, id)
++i;
}
}
Counter counter = ZI;
RunJob(shader_entries_count, RunCommandJob, JobPool_Hyper, JobPriority_Normal, &counter, .cmds = compile_cmds, .results = compile_results);
YieldOnCounter(&counter);
JobCounter counter = ZI;
RunJob(RunCommandJob,
.count = shader_entries_count,
.counter = &counter,
.sig = { .cmds = compile_cmds, .results = compile_results });
YieldOnJobs(&counter);
//- Process shader compilation results
{
@ -569,9 +566,9 @@ JobDef(StepJob, sig, id)
arc_params.arc_store = shader_store_name;
arc_params.arc_dir = shader_store_name;
Counter counter = ZI;
RunJob(1, StepJob, JobPool_Hyper, JobPriority_Normal, &counter, .params = &arc_params, .results = &arc_results);
YieldOnCounter(&counter);
JobCounter counter = ZI;
RunJob(StepJob, .counter = &counter, .sig.params = &arc_params, .sig.results = &arc_results);
YieldOnJobs(&counter);
InheritStepResults(arena, result, 1, &arc_results);
}
}
@ -645,9 +642,13 @@ JobDef(StepJob, sig, id)
++i;
}
}
Counter counter = ZI;
RunJob(dir_embeds_count, StepJob, JobPool_Hyper, JobPriority_Normal, &counter, .params = arc_params_array, .results = arc_results_array);
YieldOnCounter(&counter);
JobCounter counter = ZI;
RunJob(StepJob,
.counter = &counter,
.count = dir_embeds_count,
.sig.params = arc_params_array,
.sig.results = arc_results_array);
YieldOnJobs(&counter);
InheritStepResults(arena, result, dir_embeds_count, arc_results_array);
}
}
@ -804,7 +805,8 @@ JobDef(StepJob, sig, id)
////////////////////////////////
//~ Startup
void StartupMeta(void)
JobDecl(BuildJob, EmptySig);
JobDef(BuildJob, _, __)
{
Arena *arena = PermArena();
M_ErrorList errors = ZI;
@ -991,9 +993,9 @@ void StartupMeta(void)
resource_params->compiler_params = cp;
resource_params->flattened = flattened;
Counter step_counter = ZI;
RunJob(countof(params_array), StepJob, JobPool_Hyper, JobPriority_Normal, &step_counter, .params = params_array, .results = results_array);
YieldOnCounter(&step_counter);
JobCounter step_counter = ZI;
RunJob(StepJob, .count = countof(params_array), .counter = &step_counter, .sig.params = params_array, .sig.results = results_array);
YieldOnJobs(&step_counter);
////////////////////////////////
//~ Process compile step results
@ -1106,5 +1108,5 @@ void StartupMeta(void)
void StartupLayers(void)
{
OS_Startup();
StartupMeta();
RunJob(BuildJob, .pool = JobPool_Hyper);
}

View File

@ -53,5 +53,4 @@ void OS_Rm(String path);
////////////////////////////////
//~ @hookdecl Shell operations
void OS_Echo(String msg);
OS_CommandResult OS_RunCommand(Arena *arena, String cmd);

View File

@ -208,15 +208,6 @@ void OS_Rm(String path)
////////////////////////////////
//~ @hookdef Shell hooks
void OS_Echo(String msg)
{
HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
}
}
OS_CommandResult OS_RunCommand(Arena *arena, String cmd)
{
TempArena scratch = BeginScratch(arena);

View File

@ -75,7 +75,7 @@ Struct(MIX_Track){
};
////////////////////////////////
//~ State
//~ State types
Struct(MIX_SharedState)
{
@ -91,9 +91,7 @@ Struct(MIX_SharedState)
MIX_Track *track_last_playing;
u64 track_playing_count;
MIX_Track *track_first_free;
};
extern MIX_SharedState M_shared_state;
} extern M_shared_state;
////////////////////////////////
//~ Startup

View File

@ -279,12 +279,12 @@ Enum(P_MessageBoxKind)
void P_Startup(void);
////////////////////////////////
//~ @hookdecl Time helper operations
//~ @hookdecl Time helper hooks
P_DateTime P_LocalTime(void);
////////////////////////////////
//~ @hookdecl File system operations
//~ @hookdecl File system hooks
/* NOTE: File paths use forward slash '/' as delimiter */
@ -310,14 +310,14 @@ u64 P_GetFileSize(P_File file);
P_FileTime P_GetFileTime(P_File file);
////////////////////////////////
//~ @hookdecl File map operations
//~ @hookdecl File map hooks
P_FileMap P_OpenFileMap(P_File file);
void P_CloseFileMap(P_FileMap map);
String P_GetFileMapData(P_FileMap map);
////////////////////////////////
//~ @hookdecl Window operations
//~ @hookdecl Window hooks
P_Window *P_AcquireWindow(void);
void P_ReleaseWindow(P_Window *window);
@ -342,7 +342,7 @@ Vec2I32 P_GetWindowMonitorSize(P_Window *window);
u64 P_GetInternalWindowHandle(P_Window *window);
////////////////////////////////
//~ @hookdecl Address helpers
//~ @hookdecl Address helper hooks
P_Address P_AddressFromString(String str);
P_Address P_AddressFromIpPortCstr(char *ip_cstr, char *port_cstr);
@ -351,7 +351,7 @@ String P_StringFromAddress(Arena *arena, P_Address address);
b32 P_AddressIsEqual(P_Address a, P_Address b);
////////////////////////////////
//~ @hookdecl Sock operations
//~ @hookdecl Sock hooks
P_Sock *P_AcquireSock(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size);
void P_ReleaseSock(P_Sock *sock);
@ -359,14 +359,20 @@ P_SockReadResult P_ReadSock(Arena *arena, P_Sock *sock);
void P_WriteSock(P_Sock *sock, P_Address address, String data);
////////////////////////////////
//~ @hookdecl Utils
//~ @hookdecl Util hooks
void P_MessageBox(P_MessageBoxKind kind, String message);
void P_SetClipboardText(String str);
String P_GetClipboardText(Arena *arena);
////////////////////////////////
//~ @hookdecl Sleep
//~ @hookdecl Timer hooks
Fence *P_GetTimerFence();
i64 P_GetCurrentTimerPeriodNs();
////////////////////////////////
//~ @hookdecl Sleep hooks
void P_SleepPrecise(i64 sleep_time_ns);
void P_SleepFrame(i64 last_frame_time_ns, i64 target_dt_ns);

View File

@ -1,6 +1,3 @@
////////////////////////////////
//~ State
P_SharedLogState P_shared_log_state = ZI;
////////////////////////////////
@ -184,9 +181,9 @@ void P_Log_(i32 level, String msg)
FmtString(msg)
);
#else
String msg_formatted = FormatString(
String msg_formatted = StringF(
scratch.arena,
Lit("[%F:%F:%F.%F] |%F| [%F] %F"),
"[%F:%F:%F.%F] |%F| [%F] %F",
/* Time */
FmtUintZ(datetime.hour, 2),

View File

@ -56,9 +56,8 @@ Struct(LogEventCallback)
#define P_LogLevel_Count 6
////////////////////////////////
//~ State
//~ State types
//- Shared context
Struct(P_SharedLogState)
{
Atomic32 initialized;
@ -70,11 +69,11 @@ Struct(P_SharedLogState)
P_File file;
b32 file_valid;
};
} P_shared_log_state;
extern P_SharedLogState P_shared_log_state;
////////////////////////////////
//~ Log level types
//-- Log level settings
Struct(P_LogLevelSettings)
{
String shorthand;

View File

@ -99,6 +99,9 @@ void P_Startup(void)
//- Init winsock
WSAStartup(MAKEWORD(2, 2), &g->wsa_data);
g->socks_arena = AcquireArena(Gibi(64));
//- Init timer
RunJob(P_W32_TimerJob);
}
////////////////////////////////
@ -181,7 +184,7 @@ P_W32_Window *P_W32_AcquireWindow(void)
* created and receive a HWND, because on Windows a the event proc must run on
* the same thread that created the window. */
AddCounter(&window->ready_fence, 1);
window->window_thread = W32_AcquireThread(&P_W32_WindowThreadEntryFunc, window, Lit("Window thread"), PROF_THREAD_GROUP_WINDOW);
window->window_thread = W32_StartThread(&P_W32_WindowThreadEntryFunc, window, Lit("Window thread"), PROF_THREAD_GROUP_WINDOW);
YieldOnCounter(&window->ready_fence);
return window;
@ -193,7 +196,7 @@ void P_W32_ReleaseWindow(P_W32_Window *window)
Atomic32FetchSet(&window->shutdown, 1);
P_W32_SharedState *g = &P_W32_shared_state;
P_W32_WakeWindow(window);
W32_WaitReleaseThread(window->window_thread);
W32_WaitEndThread(window->window_thread);
Lock lock = LockE(&g->windows_mutex);
{
@ -862,6 +865,69 @@ P_Address P_W32_PlatformAddressFromWin32Address(P_W32_Address ws_addr)
return result;
}
////////////////////////////////
//~ Timer job
JobDef(P_W32_TimerJob, _, __)
{
P_W32_SharedState *g = &P_W32_shared_state;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
/* Create high resolution timer */
HANDLE timer = CreateWaitableTimerExW(0, 0, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (!timer)
{
Panic(Lit("Failed to create high resolution timer"));
}
i32 periods_index = 0;
i64 periods[P_W32_NumRollingTimerPeriods] = ZI;
for (i32 i = 0; i < (i32)countof(periods); ++i)
{
periods[i] = P_W32_DefaultTimerPeriodNs;
}
i64 last_cycle_ns = 0;
/* FIXME: shutdown */
for (;;)
{
__profn("Job scheduler cycle");
{
__profn("Job scheduler wait");
LARGE_INTEGER due = ZI;
due.QuadPart = -1;
//due.QuadPart = -10000;
//due.QuadPart = -32000;
//due.QuadPart = -12000;
//due.QuadPart = -8000;
SetWaitableTimerEx(timer, &due, 0, 0, 0, 0, 0);
WaitForSingleObject(timer, INFINITE);
}
i64 now_ns = TimeNs();
i64 period_ns = last_cycle_ns == 0 ? P_W32_DefaultTimerPeriodNs : now_ns - last_cycle_ns;
last_cycle_ns = now_ns;
/* Calculate mean period */
{
periods[periods_index++] = period_ns;
if (periods_index == countof(periods))
{
periods_index = 0;
}
f64 periods_sum_ns = 0;
for (i32 i = 0; i < (i32)countof(periods); ++i)
{
periods_sum_ns += (f64)periods[i];
}
f64 mean_ns = periods_sum_ns / (f64)countof(periods);
Atomic64FetchSet(&g->average_timer_period_ns.v, RoundF64ToI64(mean_ns));
}
/* Update fence */
FetchSetFence(&g->timer_fence, now_ns);
}
}
////////////////////////////////
//~ @hookdef Time hooks
@ -1083,7 +1149,7 @@ String P_ReadFile(Arena *arena, P_File file)
{
/* ReadFile returns non-zero on success */
/* TODO: error checking */
AlignArena(arena, 16);
PushAlign(arena, CachelineSize);
s.text = PushStructsNoZero(arena, u8, size);
LAX ReadFile(
(HANDLE)file.handle,
@ -1774,6 +1840,21 @@ String P_GetClipboardText(Arena *arena)
return result;
}
////////////////////////////////
//~ @hookdecl Timer hooks
Fence *P_GetTimerFence()
{
P_W32_SharedState *g = &P_W32_shared_state;
return &g->timer_fence;
}
i64 P_GetCurrentTimerPeriodNs()
{
P_W32_SharedState *g = &P_W32_shared_state;
return Atomic64Fetch(&g->average_timer_period_ns.v);
}
////////////////////////////////
//~ @hookdef Sleep hooks
@ -1781,31 +1862,28 @@ void P_SleepPrecise(i64 sleep_time_ns)
{
__prof;
i64 big_sleep = GetCurrentSchedulerPeriodNs();
i64 tolerance = (f64)big_sleep * 0.5;
//i64 tolerance = 1000000000;
i64 now_ns = TimeNs();
i64 target_ns = now_ns + sleep_time_ns;
/* Sleep */
while (now_ns < target_ns - big_sleep - tolerance)
/* Sleep on timer to conserve power */
{
__profn("Sleep part");
FutexYield(0, 0, 0, big_sleep);
now_ns = TimeNs();
__profn("Sleep timer");
Fence *timer_fence = P_GetTimerFence();
i64 timer_period_ns = P_GetCurrentTimerPeriodNs();
i64 timer_tolerance_ns = timer_period_ns * 0.5;
i64 target_timer_sleep_ns = target_ns - timer_period_ns - timer_tolerance_ns;
YieldOnFence(timer_fence, target_timer_sleep_ns);
}
/* Spin */
{
__profn("Sleep spin");
now_ns = TimeNs();
while (now_ns < target_ns)
{
__profn("Sleep spin");
_mm_pause();
now_ns = TimeNs();
}
}
}
void P_SleepFrame(i64 last_frame_time_ns, i64 target_dt_ns)
{

View File

@ -110,9 +110,11 @@ Struct(P_W32_Sock)
};
////////////////////////////////
//~ State
//~ State types
#define P_W32_WindowClassName L"power_play_window_class"
#define P_W32_NumRollingTimerPeriods 500
#define P_W32_DefaultTimerPeriodNs 50000000
Struct(P_W32_SharedState)
{
@ -135,9 +137,11 @@ Struct(P_W32_SharedState)
Arena *socks_arena;
Mutex socks_mutex;
P_W32_Sock *first_free_sock;
};
extern P_W32_SharedState P_W32_shared_state;
//- Timer
Fence timer_fence;
Atomic64Padded average_timer_period_ns;
} extern P_W32_shared_state;
////////////////////////////////
//~ Time operations
@ -172,3 +176,8 @@ LRESULT CALLBACK P_W32_Win32WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARA
P_W32_Address P_W32_Win32AddressFromPlatformAddress(P_Address addr);
P_W32_Address P_W32_ConvertAnyaddrToLocalhost(P_W32_Address addr);
P_Address P_W32_PlatformAddressFromWin32Address(P_W32_Address ws_addr);
////////////////////////////////
//~ Timer job
JobDecl(P_W32_TimerJob, EmptySig);

View File

@ -15,7 +15,7 @@ void PB_Startup(void)
PB_WSP_SharedState *g = &PB_WSP_shared_state;
PB_WSP_InitializeWasapi();
/* Start playback job */
RunJob(1, PB_WSP_PlaybackJob, JobPool_Audio, JobPriority_High, &g->PB_WSP_PlaybackJob_counter, 0);
RunJob(PB_WSP_PlaybackJob, .pool = JobPool_Audio, .counter = &g->shutdown_job_counter);
OnExit(&PB_WSP_Shutdown);
}
@ -24,7 +24,7 @@ ExitFuncDef(PB_WSP_Shutdown)
__prof;
PB_WSP_SharedState *g = &PB_WSP_shared_state;
Atomic32FetchSet(&g->shutdown, 1);
YieldOnCounter(&g->PB_WSP_PlaybackJob_counter);
YieldOnJobs(&g->shutdown_job_counter);
}
void PB_WSP_InitializeWasapi(void)

View File

@ -22,7 +22,7 @@ Struct(PB_WSP_Buff)
};
////////////////////////////////
//~ State
//~ State types
Struct(PB_WSP_SharedState)
{
@ -32,10 +32,8 @@ Struct(PB_WSP_SharedState)
IAudioRenderClient *playback;
WAVEFORMATEX *buffer_format;
u32 buffer_frames;
Counter PB_WSP_PlaybackJob_counter;
};
extern PB_WSP_SharedState PB_WSP_shared_state;
JobCounter shutdown_job_counter;
} extern PB_WSP_shared_state;
////////////////////////////////
//~ Wasapi startup

View File

@ -48,8 +48,8 @@ void StartupUser(void)
P_ShowWindow(g->window);
/* Start jobs */
RunJob(1, UpdateUserJob, JobPool_User, JobPriority_High, &g->shutdown_job_counters, 0);
RunJob(1, SimJob, JobPool_Sim, JobPriority_High, &g->shutdown_job_counters, 0);
RunJob(UpdateUserJob, .pool = JobPool_User, .counter = &g->shutdown_job_counter);
RunJob(SimJob, .pool = JobPool_Sim, .counter = &g->shutdown_job_counter);
OnExit(&ShutdownUser);
}
@ -61,7 +61,7 @@ ExitFuncDef(ShutdownUser)
__prof;
SharedUserState *g = &shared_user_state;
Atomic32FetchSet(&g->shutdown, 1);
YieldOnCounter(&g->shutdown_job_counters);
YieldOnJobs(&g->shutdown_job_counter);
P_ReleaseWindow(g->window);
}
@ -2310,10 +2310,10 @@ void UpdateUser(P_Window *window)
__profn("Shade pass");
GPU_ProfN(cl, Lit("Shade pass"));
u32 shade_flags = K_SHADE_FLAG_NONE;
u32 shade_flags = ShadeFlag_None;
if (effects_disabled)
{
shade_flags |= K_SHADE_FLAG_DISABLE_EFFECTS;
shade_flags |= ShadeFlag_DisableEffects;
}
ShadeSig sig = ZI;
sig.flags = shade_flags;

View File

@ -139,7 +139,7 @@ Struct(DecodeQueue)
};
////////////////////////////////
//~ State
//~ State types
Struct(BindState)
{
@ -153,7 +153,7 @@ Struct(BindState)
Struct(SharedUserState)
{
Atomic32 shutdown;
Counter shutdown_job_counters;
JobCounter shutdown_job_counter;
P_Window *window;
GPU_Swapchain *swapchain;
@ -266,9 +266,7 @@ Struct(SharedUserState)
Vec2 world_cursor;
Vec2 focus_send;
};
extern SharedUserState shared_user_state;
} extern shared_user_state;
////////////////////////////////
//~ Startup

View File

@ -277,7 +277,7 @@ void CSDef(ShadeCS, Semantic(uint3, SV_DispatchThreadID))
color *= albedo_tex[id];
/* Apply lighting */
if (!(sig.flags & K_SHADE_FLAG_DISABLE_EFFECTS))
if (!(sig.flags & ShadeFlag_DisableEffects))
{
color.rgb *= ColorFromPos(id);
}

View File

@ -60,8 +60,8 @@ AssertRootConst(FloodSig, 8);
////////////////////////////////
//~ Shade types
#define K_SHADE_FLAG_NONE (0 << 0)
#define K_SHADE_FLAG_DISABLE_EFFECTS (1 << 0)
#define ShadeFlag_None (0 << 0)
#define ShadeFlag_DisableEffects (1 << 0)
Struct(ShadeSig)
{

View File

@ -227,7 +227,7 @@ Inline Snapshot *NilSnapshot(void)
}
////////////////////////////////
//~ State
//~ State types
#define ClientLookupBinsCount 127
#define TickLookupBinsCount 127

View File

@ -98,11 +98,14 @@ AC_Asset *SND_LoadAsset(Resource resource, SND_SoundFlag flags, b32 wait)
if (is_first_touch)
{
AC_MarkLoading(asset);
SND_LoadJob_Desc *desc = PushJobDesc(SND_LoadJob, .pool = JobPool_Background, .priority = JobPriority_Low, .counter = &asset->counter);
desc->sig->resource = resource;
desc->sig->asset = asset;
desc->sig->flags = flags;
RunJobEx((GenericJobDesc *)desc);
{
Job *job = OpenJob(SND_LoadJob, JobPool_Background);
SND_LoadJob_Sig *sig = PushStruct(job->arena, SND_LoadJob_Sig);
sig->resource = resource;
sig->asset = asset;
sig->flags = flags;
CloseJob(job);
}
if (wait)
{
AC_YieldOnAssetReady(asset);

View File

@ -286,13 +286,13 @@ S_Entry *S_FetchEntry(Resource resource, JobPool pool, S_FetchFlag flags)
&& !Atomic32Fetch(&entry->texture_touched)
&& !Atomic32FetchTestSet(&entry->texture_touched, 0, 1))
{
RunJob(1, S_LoadTextureJob, pool, JobPriority_Inherit, 0, .entry = entry);
RunJob(S_LoadTextureJob, .pool = pool, .sig.entry = entry);
}
if ((flags & S_FetchFlag_Sheet)
&& !Atomic32Fetch(&entry->sheet_touched)
&& !Atomic32FetchTestSet(&entry->sheet_touched, 0, 1))
{
RunJob(1, S_LoadSheetJob, pool, JobPriority_Inherit, 0, .entry = entry);
RunJob(S_LoadSheetJob, .pool = pool, .sig.entry = entry);
}
return entry;
}

View File

@ -121,16 +121,14 @@ Struct(S_EntryBin)
};
////////////////////////////////
//~ State
//~ State types
#define S_EntryBinsCount 1024
Struct(S_SharedState)
{
S_EntryBin entry_bins[S_EntryBinsCount];
};
extern S_SharedState S_shared_state;
} extern S_shared_state;
////////////////////////////////
//~ Load jobs

View File

@ -12,7 +12,7 @@ Struct(TAR_Entry)
b32 is_dir;
TAR_Entry *next;
TAR_Entry *next_child; /* If entry is dir, points to first child. Otherwise points to next sibling. */
};
} extern Readonly TAR_nil_entry;
Struct(TAR_Archive)
{
@ -20,8 +20,6 @@ Struct(TAR_Archive)
TAR_Entry *head;
};
extern Readonly TAR_Entry TAR_nil_entry;
////////////////////////////////
//~ Header types

View File

@ -30,8 +30,7 @@ void TTF_Startup(void)
#endif
if (error != S_OK)
{
/* FIXME: Enable this */
//Panic(Lit("Error creating DWrite factory"));
Panic(Lit("Error creating DWrite factory"));
(*(volatile int *)0) = 0;
}
}

View File

@ -140,7 +140,7 @@ static inline UINT32 IDWriteGdiInterop_Release
EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE factoryType, const GUID* iid, void** factory) WIN_NOEXCEPT;
////////////////////////////////
//~ State
//~ State types
/* TODO: Determine font dpi dynamically */
#define TTF_DW_Dpi (96.0f)
@ -148,6 +148,4 @@ EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE
Struct(TTF_DW_SharedState)
{
struct IDWriteFactory5 *factory;
};
extern TTF_DW_SharedState TTF_DW_shared_state;
} extern TTF_DW_shared_state;