Files
Valiantsin DzerakhandClaude Opus 5.5 c838d9692f Count system memory in the device-local budget where device memory takes host memory
The paths that give memory back under pressure (headroom's spare block, released textures, pool mirrors) watched the
Vulkan heap budget only. On a UMA GPU that budget is gigabytes of the same RAM the game runs in, and on Windows the
AMD driver charges device-local allocations to the process commit one to one (RX 9070: VRAM use and private bytes
grew by the same 1164, 256 and 256 MiB). Crash logs show the result: the process at 4.4-10.5 GB of commit, 16 GB
machines at 80-94% RAM, std::bad_alloc inside amdvlk64.dll (#285), allocation failures on Intel UHD.

vk_mem_host_room (vk_memory.c) is what the system can still give beyond a reserve (TOTK_MEM_RESERVE_MIB, 1536):
commit, and on a UMA GPU physical memory too; Linux overcommits, so there only a UMA GPU's available memory counts.
NVIDIA is left out, not measured. vk_mem_vram caps the budget at use plus that room, and headroom keeps its spare
block only while four blocks fit in it.

RX 9070, field save, 60 s with the room near zero from the start (TOTK_MEM_RESERVE_MIB=21000): commit 7124 MB instead
of 8965, VRAM 2631 MiB instead of 3781, released textures 153 instead of 1866; mirrors refused for 423 MiB of pools,
GPU 4.45 ms a frame instead of 3.34 ms, 60 fps either way. Without pressure nothing changes. The census logs the
room, the device class line the mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:02:36 +03:00

473 lines
34 KiB
C++

// Platform layer: threads, time, locks, atomics, files. Runtime code calls this instead of the OS; one implementation
// per OS (win32.c, linux.c). The FP control register lives in the per-architecture fp_*.h headers, inline, because
// lifted FP code touches it on every operation.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#if defined(__x86_64__) || defined(_M_X64)
#include "fp_x86.h"
#include <immintrin.h>
#endif
// ---- threads
typedef struct PlatThread PlatThread;
typedef void (*PlatThreadFn)(void *arg);
// Starts fn(arg) on a new thread named name (shown in debuggers and profilers; may be NULL). NULL on failure.
PlatThread *plat_thread_new(PlatThreadFn fn, void *arg, const char *name);
// The same with the stack size given: stack bytes reserved for the thread (0: PLAT_STACK_DEFAULT). The default is the
// same on every OS, so a thread that fits on Windows fits elsewhere.
#define PLAT_STACK_DEFAULT (16u << 20)
PlatThread *plat_thread_new_ex(PlatThreadFn fn, void *arg, const char *name, size_t stack);
// Starts a thread nobody joins (plat_thread_new_ex + plat_thread_detach). 1 on success.
int plat_thread_spawn(PlatThreadFn fn, void *arg, const char *name, size_t stack);
void plat_thread_join(PlatThread *t); // waits for fn to return, then frees t
// Waits at most ns for fn to return: 1 when it did (t is freed), 0 when it still runs (t stays valid).
int plat_thread_join_ns(PlatThread *t, uint64_t ns);
void plat_thread_detach(PlatThread *t); // frees t; the thread runs on
// Restricts t (NULL: the calling thread) to the logical processors in mask (bit n = processor n of the first group).
// Returns the previous mask, 0 on failure.
uint64_t plat_thread_affinity(PlatThread *t, uint64_t mask);
uint32_t plat_cpu_count(void); // logical processors of the machine (system info)
uint32_t plat_cpu_usable(void); // the ones this process may run on (its affinity mask): size thread pools by it
uint64_t plat_thread_cpu_ns(uint32_t tid); // CPU time of the thread with id tid (plat_thread_id) so far; 0 unknown
// The logical processors the process may use (bit n = processor n, the first 64), into *all, and the lowest of each
// physical core among them into *first; returns the number of physical cores, 0 when unknown.
int plat_cpu_cores(uint64_t *all, uint64_t *first);
int plat_process_affinity(uint64_t mask); // every thread of the process to the processors in mask; 1 on success
// Interrupting a thread (kernel.c's asynchronous preemption). A thread makes its own handle; another thread stops it
// wherever it runs, reads where it is, may send it elsewhere, and lets it go on. While it is stopped the caller must not
// wait for anything the stopped thread could hold.
typedef struct PlatIntr PlatIntr;
PlatIntr *plat_intr_self(void); // NULL on failure
void plat_intr_free(PlatIntr *t);
// Stops t and reads its instruction and stack pointers: 1 when stopped, 0 when it did not stop in time (a thread
// blocked in the OS); t then runs on untouched.
int plat_intr_stop(PlatIntr *t, uint64_t *rip, uint64_t *rsp);
// Lets a stopped t go on: at to when to is nonzero, else where it stopped. 0 when the redirection failed (t then goes
// on where it stopped).
int plat_intr_resume(PlatIntr *t, uint64_t to);
// Fibers: execution contexts with stacks of their own that threads switch between; a fiber may resume on another
// thread. Each switch keeps the callee-saved registers and the floating-point control state.
typedef struct PlatFiber PlatFiber;
PlatFiber *plat_fiber_self(void); // the calling thread's own context as a fiber, once before its first switch
void plat_fiber_unself(void); // the calling thread stops being a fiber (it is on its own context again)
// A fiber that runs fn(arg) on a stack of stack bytes (reserved, committed as it grows); fn never returns.
PlatFiber *plat_fiber_new(size_t stack, void (*fn)(void *arg), void *arg);
void plat_fiber_switch(PlatFiber *to); // from the fiber the calling thread runs
void plat_fiber_free(PlatFiber *f); // a fiber nobody runs
// A jump back to a point saved earlier on the same thread, without unwinding the frames in between (no SEH, no C++
// cleanups): the kernel's ExitThread leaves the lifted frames this way. plat_jmp_save returns 0 when it saves and 1
// when plat_jmp_go comes back to it; the function that called it must still be running. It keeps the callee-saved
// registers and the floating-point control state. On Windows this is not the C runtime's setjmp: a non-unwinding
// setjmp leaves the buffer's frame 0, and the C runtime's longjmp ends the process with 0xc0000409 (fast fail 13) on
// such a buffer when Control Flow Guard is on for the process, which Exploit protection can force on any program.
#ifdef _WIN32
typedef struct { uint64_t gpr[10]; uint32_t mxcsr, fpcw; uint64_t pad; uint8_t xmm[160]; } PlatJmp;
int plat_jmp_save(PlatJmp *j) __attribute__((returns_twice, nothrow));
_Noreturn void plat_jmp_go(PlatJmp *j);
#else
#include <setjmp.h>
typedef struct { jmp_buf b; } PlatJmp;
#define plat_jmp_save(j) setjmp((j)->b)
#define plat_jmp_go(j) longjmp((j)->b, 1)
#endif
// Scheduling priority, Windows' seven levels: of the calling thread, or of t. On Linux a thread lowered without the
// right to raise its priority again (CAP_SYS_NICE, or an RLIMIT_NICE of 20) could never return to normal, and the
// runtime lowers threads that later take shared locks at normal priority; without that right every change is skipped
// and threads keep the normal priority. Levels above normal need the same right.
enum { PLAT_PRIO_IDLE = -15, PLAT_PRIO_LOWEST = -2, PLAT_PRIO_BELOW_NORMAL = -1, PLAT_PRIO_NORMAL = 0,
PLAT_PRIO_ABOVE_NORMAL = 1, PLAT_PRIO_HIGHEST = 2, PLAT_PRIO_TIME_CRITICAL = 15 };
void plat_thread_priority(int prio);
void plat_thread_set_priority(PlatThread *t, int prio);
int plat_thread_priority_get(void); // the calling thread's level
void plat_yield(void); // gives the rest of the time slice to another ready thread
static inline void plat_pause(void) { // spin-wait hint
#if defined(__x86_64__) || defined(_M_X64)
_mm_pause();
#endif
}
// ---- time
uint64_t plat_now_ns(void); // monotonic, high resolution, arbitrary epoch
uint64_t plat_counter(void); // the OS's raw monotonic counter (QPC on Windows), plat_counter_hz per second
uint64_t plat_counter_hz(void);
void plat_timer_1ms(void); // asks the OS for timed waits accurate to 1 ms or better (call before threads)
int plat_power_full(void); // opts the process out of OS power throttling: 1 done, 0 refused, -1 no such thing
// Hybrid CPUs: finds the performance-core logical processors the process may use, once, before any thread asks; returns
// their count (0: none to prefer, why says which case). plat_thread_perf_core: the calling thread prefers them; 1 done,
// 0 refused, -1 nothing selected.
int plat_perf_core_setup(char *why, size_t n);
int plat_thread_perf_core(void);
// t * num / den for a fixed num / den without a 128-bit division per call (__udivti3 took 5% of the game's running
// samples through plat_now_ns and rt_ticks): t * q + (t * f >> 64) with q = num / den and f = (num % den) * 2^64 / den.
// Monotonic in t and at most one below the exact quotient while t * (num % den) / den fits 64 bits.
typedef struct { uint64_t q, f; } PlatScale;
static inline PlatScale plat_scale(uint64_t num, uint64_t den) {
return (PlatScale){num / den, (uint64_t)(((unsigned __int128)(num % den) << 64) / den)};
}
static inline uint64_t plat_scaled(PlatScale s, uint64_t t) {
return t * s.q + (uint64_t)(((unsigned __int128)t * s.f) >> 64);
}
void plat_sleep_ns(uint64_t ns); // returns no earlier than ns later; overshoot well under 1 ms
// Sleeps about ns on the OS's high-resolution timer without spinning: wakes up to PLAT_WAIT_LATE_NS late, never early.
void plat_wait_ns(uint64_t ns);
#ifdef _WIN32
#define PLAT_WAIT_LATE_NS 500000u // high-resolution waitable timer
#else
#define PLAT_WAIT_LATE_NS 60000u // clock_nanosleep with a 1 us timer slack (plat_timer_1ms), scheduler wakeup
#endif
static inline uint64_t plat_now_ms(void) { return plat_now_ns() / 1000000u; }
void plat_sleep_ms(uint32_t ms); // coarse sleep: the scheduler's granularity, no spinning
// ---- locks and condition variables. A PlatMutex is a reader-writer lock taken exclusively (plat_mutex_lock) or
// shared (plat_mutex_lock_shared), not recursive, like Windows' SRW lock it is on Windows. Zero-initialised storage is
// a valid unlocked lock and an empty condition variable (PLAT_MUTEX_INIT / PLAT_COND_INIT spell it).
typedef struct { void *p; } PlatMutex;
typedef struct { void *p; } PlatCond;
#define PLAT_MUTEX_INIT {0}
#define PLAT_COND_INIT {0}
void plat_mutex_init(PlatMutex *m);
void plat_mutex_lock(PlatMutex *m);
int plat_mutex_trylock(PlatMutex *m); // 1 when acquired
void plat_mutex_unlock(PlatMutex *m);
void plat_mutex_lock_shared(PlatMutex *m);
int plat_mutex_trylock_shared(PlatMutex *m); // 1 when acquired
void plat_mutex_unlock_shared(PlatMutex *m);
void plat_cond_init(PlatCond *c);
void plat_cond_wait(PlatCond *c, PlatMutex *m); // m held exclusively
int plat_cond_wait_ns(PlatCond *c, PlatMutex *m, uint64_t ns); // 0 on timeout (spurious wakeups return 1 too)
static inline int plat_cond_wait_ms(PlatCond *c, PlatMutex *m, uint32_t ms) {
return plat_cond_wait_ns(c, m, (uint64_t)ms * 1000000u);
}
void plat_cond_signal(PlatCond *c);
void plat_cond_broadcast(PlatCond *c);
// A mutex the thread that holds it may take again; unlocked as many times as locked. Zero-initialised is unlocked.
typedef struct { PlatMutex m; uint32_t owner, depth; } PlatRMutex;
#define PLAT_RMUTEX_INIT {PLAT_MUTEX_INIT, 0, 0}
void plat_rmutex_lock(PlatRMutex *m);
void plat_rmutex_unlock(PlatRMutex *m);
// One-time initialisation: the first caller runs fn(arg), every other caller waits until it returned. Zero-initialised
// (PLAT_ONCE_INIT) has not run.
typedef struct { volatile int32_t state; } PlatOnce;
#define PLAT_ONCE_INIT {0}
void plat_once(PlatOnce *o, void (*fn)(void *arg), void *arg);
// An event: set wakes its waiters; an auto-reset event lets one waiter through and clears itself, a manual-reset one
// stays set until reset.
typedef struct { PlatMutex m; PlatCond c; int set, manual; } PlatEvent;
void plat_event_init(PlatEvent *e, int manual, int initially_set);
void plat_event_set(PlatEvent *e);
void plat_event_reset(PlatEvent *e);
void plat_event_wait(PlatEvent *e);
int plat_event_wait_ns(PlatEvent *e, uint64_t ns); // 1 when the event was set, 0 on timeout
// ---- atomics: full barriers, explicit widths (Windows' Interlocked* operate on 32-bit LONG). The value returned is
// noted per function: new for inc/dec/add, previous for xadd/xchg/cas/or.
#define PLAT_SC __ATOMIC_SEQ_CST
static inline int32_t plat_inc32(volatile int32_t *p) { return __atomic_add_fetch(p, 1, PLAT_SC); }
static inline int32_t plat_dec32(volatile int32_t *p) { return __atomic_sub_fetch(p, 1, PLAT_SC); }
static inline int32_t plat_add32(volatile int32_t *p, int32_t v) { return __atomic_add_fetch(p, v, PLAT_SC); }
static inline int32_t plat_xadd32(volatile int32_t *p, int32_t v) { return __atomic_fetch_add(p, v, PLAT_SC); }
static inline int32_t plat_xchg32(volatile int32_t *p, int32_t v) { return __atomic_exchange_n(p, v, PLAT_SC); }
static inline int32_t plat_cas32(volatile int32_t *p, int32_t xchg, int32_t cmp) {
__atomic_compare_exchange_n(p, &cmp, xchg, 0, PLAT_SC, PLAT_SC);
return cmp;
}
static inline int64_t plat_inc64(volatile int64_t *p) { return __atomic_add_fetch(p, 1, PLAT_SC); }
static inline int64_t plat_dec64(volatile int64_t *p) { return __atomic_sub_fetch(p, 1, PLAT_SC); }
static inline int64_t plat_add64(volatile int64_t *p, int64_t v) { return __atomic_add_fetch(p, v, PLAT_SC); }
static inline int64_t plat_xadd64(volatile int64_t *p, int64_t v) { return __atomic_fetch_add(p, v, PLAT_SC); }
static inline int64_t plat_xchg64(volatile int64_t *p, int64_t v) { return __atomic_exchange_n(p, v, PLAT_SC); }
static inline int64_t plat_or64(volatile int64_t *p, int64_t v) { return __atomic_fetch_or(p, v, PLAT_SC); }
static inline int64_t plat_cas64(volatile int64_t *p, int64_t xchg, int64_t cmp) {
__atomic_compare_exchange_n(p, &cmp, xchg, 0, PLAT_SC, PLAT_SC);
return cmp;
}
static inline void *plat_xchgp(void *volatile *p, void *v) { return __atomic_exchange_n(p, v, PLAT_SC); }
static inline void *plat_casp(void *volatile *p, void *xchg, void *cmp) {
__atomic_compare_exchange_n(p, &cmp, xchg, 0, PLAT_SC, PLAT_SC);
return cmp;
}
// ---- virtual memory: ranges reserved at fixed addresses below 2^39, their pages committed, decommitted and protected
// at 4 KiB granularity.
enum { PLAT_PROT_NONE, PLAT_PROT_READ, PLAT_PROT_RW };
enum { PLAT_MEM_FREE = -2, PLAT_MEM_RESERVED = -1 }; // plat_mem_state besides PLAT_PROT_* for committed pages
// Reserves [addr, addr + n) inaccessible, exactly there: 0 when any of it is taken. watch: writes to its pages are
// tracked for plat_mem_written.
int plat_mem_reserve(uint64_t addr, uint64_t n, int watch);
// n bytes of read-write memory anywhere, zero, backed only as pages are first written (plat_mem_protect may seal them);
// NULL on failure. Kept until exit.
void *plat_mem_alloc(size_t n);
int plat_mem_commit(uint64_t addr, size_t n); // read-write pages inside a reserved range; 1 on success
int plat_mem_decommit(uint64_t addr, uint64_t n); // contents dropped: committed again, the pages read zero
int plat_mem_protect(uint64_t addr, uint64_t n, int prot); // committed pages only; 1 on success
// The page at addr: PLAT_PROT_* when committed, else PLAT_MEM_RESERVED or PLAT_MEM_FREE (not reserved by us). *end
// gets the end of the run of pages in the same state, at most limit.
int plat_mem_state(uint64_t addr, uint64_t limit, uint64_t *end);
// The pages of a watched reservation written since the last reset, up to max page addresses into pages, in address
// order; reset: those reported start over as unwritten. UINT64_MAX when writes to the range are not tracked.
uint64_t plat_mem_written(uint64_t addr, uint64_t n, uint64_t *pages, uint64_t max, int reset);
// A write that faulted on a read-only page at addr goes to fn first; fn returns 1 when it made the page writable and
// the write should run again, 0 to let the fault go on (to the crash handler). One hook per process.
void plat_mem_fault_hook(int (*fn)(uint64_t addr));
// Starts this program again with the same arguments and ends this process with the new one's exit code (Windows), or
// replaces this process with it (Linux). Environment changes made before the call are passed on.
_Noreturn void plat_reexec(void);
// A crash goes to fn, which reports it and must end the process. The write-fault hook above runs first.
// - A fault in this executable's own code (an access violation, an illegal instruction, a division by zero, a stack
// overflow) is reported as it happens.
// - A fault in another module (a GPU driver, a system library) is reported only when nothing handles it: on Windows
// from the unhandled-exception filter, with the return addresses on the faulting thread's stack in every module, so
// the report shows which driver code failed and which host call ran into it; on Linux from the signal handler,
// without frames. Drivers that raise and catch their own exceptions never reach fn. For a C++ exception thrown by
// MSVC-built code (a driver) the report names its type and, for a std::exception, its what() text.
// - On Windows abort() (a failed assert, std::terminate) and an invalid parameter to a C runtime function go there
// too, with the host frames in this executable: the C runtime ends the process for both with 0xc0000409 and no
// exception.
typedef struct {
const char *what; // "access violation", "illegal instruction", ...
uint32_t code; // the Windows exception code or the signal number
uintptr_t rip, offset; // the faulting instruction, and its offset in its module
int access; // the data access that faulted: 0 read, 1 write, 8 execute; -1 when not an access
uint64_t addr; // its address when access >= 0
int nframes; // return addresses, innermost first: for abort and invalid parameter (rip is frames[0])
uintptr_t frames[32]; // and for a fault in another module; 0 for a fault in this one
const char *frame_module[32]; // the module of each frame: NULL for this executable, "" for none
uintptr_t frame_offset[32]; // each frame's offset in its module
int frames_left_out; // frames in other modules past the limit the walk keeps, so this executable's still fit
const char *module; // the module holding rip: NULL for this executable, "" for none (a jump to address 0)
const char *cxx_type; // a C++ exception from MSVC-built code: the thrown type's decorated name (.?AV...@@)
const char *cxx_what; // and what() when the type derives from std::exception; NULL when unknown
int has_regs; // gpr holds the faulting thread's registers (a fault; not abort or invalid parameter)
uint64_t gpr[16]; // rax rcx rdx rbx rsp rbp rsi rdi r8..r15, in the x86 encoding order
} PlatCrash;
#define PLAT_GPR_NAMES {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", \
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}
void plat_crash_hook(void (*fn)(const PlatCrash *c));
int plat_mem_read(uint64_t addr, void *out, size_t n); // copies n bytes, 0 instead of faulting on unmapped memory
int plat_screen_size(int *w, int *h); // the primary display's current mode in pixels; 0 unknown
int plat_mem_readable(uint64_t addr, uint64_t n); // every page of [addr, addr + n) can be read; no fault
void *plat_aligned_alloc(size_t size, size_t align); // free with plat_aligned_free; NULL on failure
void plat_aligned_free(void *p);
// ---- process
_Noreturn void plat_exit(int code); // ends the process at once: other threads stop, no atexit handlers run
int plat_setenv(const char *name, const char *value); // value NULL removes it; 1 on success
uint32_t plat_process_id(void);
// CPU time of the process since it started (user and kernel), and wall time since it started, in ns; 0 when unknown.
uint64_t plat_process_cpu_ns(void);
uint64_t plat_process_uptime_ns(void);
uint64_t plat_unix_time_ns(void); // wall clock, ns since 1970
void plat_local_time(struct tm *out); // the wall clock now in the local time zone
int plat_localtime(int64_t unix_s, struct tm *out); // a POSIX time in the local time zone; 0 out of range
int plat_gmtime(int64_t unix_s, struct tm *out); // the same in UTC
long plat_tz_west(void); // the local zone's standard offset, seconds west of UTC (UTC-5: 18000)
// Runs this executable again with the same arguments and env_name=env_value added to its environment, for probes that
// expect the child to stop with a fatal. Its stdout and stderr go into out (NUL-terminated, the rest discarded); waits
// at most timeout_ms (0: no limit) and kills it after that. Returns the exit code, -1 when it could not start.
int plat_run_self(const char *env_name, const char *env_value, char *out, size_t cap, uint32_t timeout_ms);
// ---- randomness
int plat_random(void *out, size_t n); // cryptographically random bytes; 1 on success
// ---- files. Paths are UTF-8 with either separator; each implementation converts to what the OS wants.
int plat_file_exists(const char *path); // 1 for a file (not a directory)
int plat_dir_exists(const char *path); // 1 for a directory
// path made absolute against the working directory (not resolved further); 0 when it does not fit cap.
int plat_full_path(const char *path, char *out, size_t cap);
void plat_file_mkdir_p(const char *path); // creates every missing directory of path, itself included
// Whole file into a malloc'd buffer with a NUL after the last byte; NULL when missing, empty or over 1 GiB.
uint8_t *plat_file_read(const char *path, uint64_t *size);
int plat_file_write(const char *path, const void *data, size_t n); // create or truncate; 1 when all n bytes landed
int plat_file_replace(const char *from, const char *to); // rename, replacing to (write-through); 1 on success
// Writes path.<pid>.<tid>.tmp, flushes it to the disk and renames it over path: a crash leaves the old file or the new
// one, never a torn or zero-filled one. 1 on success.
int plat_file_write_atomic(const char *path, const void *data, size_t n);
int plat_file_flush(const char *path); // flushes a written file's data to the disk; 1 on success
int plat_file_sync(FILE *f); // fflush, then the file's data to the disk; 1 on success
// fseek and ftell with 64-bit offsets (a long is 32 bits on Windows).
#ifdef _WIN32
static inline int plat_fseek64(FILE *f, int64_t off, int whence) { return _fseeki64(f, off, whence); }
static inline int64_t plat_ftell64(FILE *f) { return _ftelli64(f); }
#else
static inline int plat_fseek64(FILE *f, int64_t off, int whence) { return fseeko(f, (off_t)off, whence); }
static inline int64_t plat_ftell64(FILE *f) { return (int64_t)ftello(f); }
#endif
void plat_file_delete(const char *path);
int plat_file_truncate(const char *path, uint64_t size); // 1 on success
int64_t plat_file_size(const char *path); // bytes of a file, -1 when missing or unreadable
int plat_file_set_readonly(const char *path, int on); // sets or clears the read-only attribute; 1 on success
// A file that only grows and other processes may read meanwhile: each write lands whole at its end, from any thread,
// and reaches the file even if the process dies right after (no user-space buffer, no flush to the disk).
typedef struct PlatAppend PlatAppend;
PlatAppend *plat_append_open(const char *path); // creates or truncates path; NULL on failure
int plat_append_write(PlatAppend *f, const void *data, size_t n); // 1 when all n bytes landed
// Calls cb(name, ctx) for each file in dir whose name ends in ext (".in"); name has no directory part. cb returns 0 to
// stop. Returns 0 when dir cannot be listed.
int plat_file_list(const char *dir, const char *ext, int (*cb)(const char *name, void *ctx), void *ctx);
// Open files for positional reads: any thread, in parallel on one handle, no file pointer.
typedef struct PlatFile PlatFile;
PlatFile *plat_file_open(const char *path); // read-only; NULL when missing or unreadable
// The same without the OS's file cache (reads go to the disk into the buffer): offsets, sizes and buffers aligned to
// the sector size.
PlatFile *plat_file_open_unbuffered(const char *path);
void plat_file_close(PlatFile *f);
// Bytes read at off (0 at or past the end of the file), -1 on an I/O error, -2 when the OS refused to write into buf
// (a read-only page).
int64_t plat_file_pread(PlatFile *f, void *buf, uint32_t n, uint64_t off);
// What changes when the file is replaced or written: its volume, file id, size, last write and creation (on Linux,
// status change) times in the OS's units. 0 on failure.
typedef struct { uint64_t volume, file_id, size, write_time, create_time; } PlatFileId;
int plat_file_id(PlatFile *f, PlatFileId *out);
const void *plat_file_map(PlatFile *f); // a read-only view of the whole file, kept until exit; NULL
// ---- the host file system under the game's save data (fs_host.c). Calls that can fail return PLAT_FS_OK (0) or the
// reason; other processes may open the same files meanwhile.
enum { PLAT_FS_OK, PLAT_FS_NOT_FOUND, PLAT_FS_EXISTS, PLAT_FS_NOT_EMPTY, PLAT_FS_BUSY, PLAT_FS_ERROR };
enum { PLAT_FS_MISSING = -1, PLAT_FS_FILE, PLAT_FS_DIR, PLAT_FS_LINK }; // plat_fs_kind; LINK: a symbolic link or
// reparse point, never followed
int plat_fs_kind(const char *path);
int plat_fs_os_error(void); // the OS error code (GetLastError, errno) of this thread's last failed plat_fs_* call
int plat_fs_mkdir(const char *path); // PLAT_FS_EXISTS when something is there
int plat_fs_rmdir(const char *path); // an empty directory (a read-only one too), or a link to one
int plat_fs_unlink(const char *path); // a file or a link; a read-only one too
// Renames a file or directory; never replaces (PLAT_FS_EXISTS). durable: the change is on the disk at return.
int plat_fs_rename(const char *from, const char *to, int durable);
int plat_fs_link(const char *existing, const char *path); // a hard link
int plat_fs_links(const char *path); // the file's hard link count, 0 when unknown
void plat_fs_sync_dir(const char *path); // a directory's entries to the disk, as far as the FS allows
// Why dir could not be renamed, for the log: into out "no permission to rename it" when this process may not delete
// it, else the programs holding a file under it ("explorer.exe 1234, ..."), else an empty string. Windows refuses to
// rename a directory with any file open under it; elsewhere out is always empty.
void plat_fs_busy_reason(const char *dir, char *out, size_t cap);
// cb(name, kind, size, ctx) for each entry of dir but "." and ".."; cb returns 0 to stop. The listing's error.
int plat_fs_list(const char *dir, int (*cb)(const char *name, int kind, uint64_t size, void *ctx), void *ctx);
// An existing file for reading and, with write, writing; *err (may be NULL) gets the reason on failure.
PlatFile *plat_fs_open(const char *path, int write, int *err);
PlatFile *plat_fs_create(const char *path, int *err); // a new file for writing; PLAT_FS_EXISTS when there
int64_t plat_file_pwrite(PlatFile *f, const void *buf, uint32_t n, uint64_t off); // bytes written, -1 on error
int plat_file_set_size(PlatFile *f, uint64_t size); // PLAT_FS_*
int plat_file_fsync(PlatFile *f); // the file's data to the disk; 1 on success
int plat_temp_dir(char *out, size_t cap); // the user's temporary directory, no trailing separator
int plat_exe_path(char *out, size_t cap); // path of the running executable; 0 on failure
uintptr_t plat_exe_base(void); // where the executable is loaded
// The module (executable or library) holding code address addr: its file name without directories into name, its load
// address into *base. 0 when none.
int plat_module_of(uintptr_t addr, char *name, size_t cap, uintptr_t *base);
#define PLAT_PATH_MAX 1024 // path buffers of the runtime
#ifdef _WIN32
#define PLAT_EXE_SUFFIX ".exe" // of the programs the runtime starts (totk_xlate)
#else
#define PLAT_EXE_SUFFIX ""
#endif
// The last directory separator in path ('/' or '\\'), NULL when there is none.
static inline char *plat_last_sep(const char *path) {
char *last = NULL;
for (const char *p = path; *p; p++)
if (*p == '/' || *p == '\\') last = (char *)p;
return last;
}
// Environment variable as UTF-8 into out; its length, or 0 when unset, empty or too long for cap.
size_t plat_getenv(const char *name, char *out, size_t cap);
// The user's UI language as a BCP-47 tag ("ru-RU", "en-US"); "" when the system gives none. Valid for the process.
const char *plat_ui_language(void);
// The user's regional format (locale) as a BCP-47 tag; "" when the system gives none. Valid for the process.
const char *plat_user_locale(void);
uint32_t plat_thread_id(void); // id of the calling thread, for unique file names
// A system font file with the glyphs of a catalog language ("ja", "ko", "zh-Hans", "zh-Hant"): the menu shows each
// language's name in its own script. Copies the UTF-8 path into out; 0 when the system has none.
int plat_font_path(const char *lang, char *out, size_t cap);
// The operating system and its build ("Windows 10.0.28000") into out; 0 when unknown.
int plat_os_version(char *out, size_t cap);
int plat_is_wine(void); // 1 when the Windows build runs under Wine; 0 elsewhere
// The file the process's stderr (the log) goes to, as a UTF-8 path; 0 when stderr is not a file (a console, a pipe).
int plat_stderr_path(char *out, size_t cap);
// ---- desktop: the in-game menu's "Copy system info" and "Open log folder"
int plat_clipboard_set(const char *utf8); // replaces the clipboard's text; 1 on success
int plat_open_folder(const char *path); // shows the folder in the file manager; 1 on success
// ---- dynamic libraries: a library some machines lack (a GPU vendor's), loaded at run time. A bare name is searched
// in the system directories only. NULL when missing.
void *plat_lib_open(const char *name);
void *plat_lib_sym(void *lib, const char *name);
// Why the calling thread's last plat_lib_open failed, for a log line (the OS error code on Windows, dlerror on Linux).
void plat_lib_error(char *out, size_t cap);
// ---- NVIDIA NGX (vk_dlaa.c). The path of the driver's NGX core library into out: on Windows _nvngx.dll in the folder
// the driver records in the registry (NGXCore FullPath), on Linux libnvidia-ngx.so.1, which the dynamic loader finds
// where the driver put it. 0 when the driver records none.
int plat_ngx_core_path(char *out, size_t cap);
// The running executable's folder with a trailing separator, as the wchar_t string NGX takes for its application data
// path (UTF-16 on Windows, UTF-32 code points on Linux). 0 on failure or when out has no room.
int plat_exe_dir_wide(wchar_t *out, size_t cap);
// ---- machine load for the performance overlay (perfmon.c's sampling thread; each call can take milliseconds). A number
// the system does not give is -1.
typedef struct PlatLoad PlatLoad;
// Opens the counters: CPU times, the thermal zones, and the GPU engine and memory counters of the adapter whose LUID is
// gpu_luid (8 bytes, Vulkan's deviceLUID; NULL: the busiest adapter) or, where the system names GPUs by PCI address,
// on PCI bus pci_bus (-1: unknown). Never NULL.
PlatLoad *plat_load_open(const uint8_t *gpu_luid, int pci_bus);
void plat_load_close(PlatLoad *l);
// Since the previous call on l (the first call gives -1): CPU use of the machine and of this process, percent of all
// logical processors, and the hottest ACPI thermal zone in degrees Celsius.
void plat_cpu_load(PlatLoad *l, float *total_pct, float *proc_pct, float *temp_c);
// Since the previous call on l: the busiest GPU engine type in percent (as Task Manager shows it), and the adapter's
// dedicated memory in use in MB. Any GPU vendor.
void plat_gpu_load(PlatLoad *l, float *load_pct, float *vram_used_mb);
// Physical memory in MB: in use and installed on the machine, and this process's working set.
void plat_mem_info(float *used_mb, float *total_mb, float *proc_mb);
// Commit charge in MB: this process's private bytes, the system's commit in use and its limit (RAM plus page files).
// An allocation fails when the system's reaches the limit, however much physical memory is free. -1 when unknown.
void plat_commit_info(double *proc_mb, double *used_mb, double *limit_mb);
// What the system can still give, in bytes: commit (Windows: limit minus charge; Linux, which overcommits: UINT64_MAX)
// and physical memory available without paging. UINT64_MAX when unknown.
void plat_mem_room(uint64_t *commit_room, uint64_t *phys_room);
// This process's memory in bytes: resident (the working set) and private (Windows: committed private bytes; Linux: the
// resident anonymous pages). 0 when unknown.
void plat_process_memory(uint64_t *resident, uint64_t *private_bytes);
// ---- child processes: a program with stdin and stdout piped to us (stderr discarded), no console window.
typedef struct PlatChild PlatChild;
// Starts "exe" followed by arg (arg may be NULL). NULL on failure; *err (may be NULL) gets the OS error code.
// Children start one at a time so none inherits another pipe ends.
PlatChild *plat_child_start(const char *exe, const char *arg, uint32_t *err);
void plat_child_stop(PlatChild *c); // kills the child and frees c
int plat_child_write(PlatChild *c, const void *data, size_t n); // 1 when all n bytes were written
long plat_child_read(PlatChild *c, void *buf, size_t cap); // bytes read (at least 1); 0 or negative: pipe broke
// ---- local IPC: a stream connection to another program of this machine, one listen location per name (Discord's
// Rich Presence, discord_rpc.c). On Windows the named pipe \\.\pipe\<name>; on Linux the Unix domain socket
// <dir>/<name> with <dir> the first of $DISCORD_IPC_PATH, $XDG_RUNTIME_DIR, $TMPDIR and /tmp that has one.
typedef struct PlatIpc PlatIpc;
PlatIpc *plat_ipc_connect(const char *name); // NULL when nothing listens under that name
// Up to n bytes, waiting at most ms for the first of them: bytes read, 0 none in time, -1 the connection broke.
// The rest of what the peer wrote may still be in its buffer: a caller that needs a whole frame loops.
int plat_ipc_read(PlatIpc *p, void *buf, size_t n, uint32_t ms);
int plat_ipc_write(PlatIpc *p, const void *buf, size_t n); // 1 when all n bytes were written, 0 the connection broke
void plat_ipc_close(PlatIpc *p);
// ---- restart: this program again, in a new process that starts once this one is gone
// Starts this executable again with the same command line, environment and working folder, and no handle of this
// process. 1 when it started; the caller then exits.
int plat_restart(void);
// Starts argv[0] with the arguments argv[1..] (NULL-terminated, UTF-8) in folder cwd, detached from this process: no
// console, no handle of this one. 1 when it started (the setup window that rebuilds the game, runtime/cheats.c).
int plat_spawn(const char *const *argv, const char *cwd);
// In a process plat_restart started: waits, at most timeout_ms, until the process that started it has exited. 1 after
// the wait, -1 on timeout, 0 when there is nothing to wait for (not started by plat_restart, or that process is gone).
int plat_restart_wait(uint32_t timeout_ms);