rtengine/src/runtime/config.c
Kevin Trogant 4e02d43514 Add early config file parsing
Also cleaned up contrib/ and made lz4 and xxhash subprojects installed
via wrap.
2024-07-19 10:30:50 +02:00

304 lines
9.5 KiB
C

#include "config.h"
#include "fsutils.h"
#include "runtime.h"
#include "threading.h"
#include "aio.h"
#include "buffer_manager.h"
#include "file_tab.h"
#include "mem_arena.h"
#include "string_storage.h"
#include <ini.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#define RT_MAX_CVARS 1024
#define RT_MAX_EVENT_HANDLERS 1024
typedef struct rt_cvar_change_event_list {
rt_cvar_change_event_handler_fn *Handler;
void *userdata;
struct rt_cvar_change_event_list *next;
} rt_cvar_change_event_list;
typedef struct {
rt_cvar *cvar;
rt_cvar_change_event_list *change_event_handlers;
} rt_cvar_container;
static rt_cvar_container _vars[RT_MAX_CVARS];
static unsigned int _next_cvar = 0;
static rt_cvar_change_event_list _change_event_handlers[RT_MAX_EVENT_HANDLERS];
static unsigned int _next_event_handler = 0;
static rt_mutex *_mutex = NULL;
RT_DLLEXPORT void rtRegisterCVAR(rt_cvar *cvar) {
if (!_mutex)
_mutex = rtCreateMutex();
rtLockMutex(_mutex);
if (_next_cvar < RT_MAX_CVARS) {
rtLog("CVAR", "Registered cvar %s", cvar->name);
_vars[_next_cvar].cvar = cvar;
_vars[_next_cvar].change_event_handlers = NULL;
++_next_cvar;
} else {
rtReportError("CVAR", "Ran out of space for CVars");
}
rtUnlockMutex(_mutex);
}
RT_DLLEXPORT rt_cvar *rtGetCVAR(const char *name) {
if (!_mutex)
_mutex = rtCreateMutex();
rt_cvar *var = NULL;
rtLockMutex(_mutex);
for (unsigned int i = 0; i < _next_cvar; ++i) {
if (strcmp(name, _vars[i].cvar->name) == 0) {
var = _vars[i].cvar;
break;
}
}
rtUnlockMutex(_mutex);
return var;
}
RT_DLLEXPORT void rtRegisterCVARChangeEventHandler(rt_cvar *cvar,
rt_cvar_change_event_handler_fn *event_handler,
void *userdata) {
RT_VERIFY(_mutex);
rtLockMutex(_mutex);
if (_next_event_handler == RT_MAX_EVENT_HANDLERS) {
rtReportError("CVAR", "Ran out of space for CVar change event handlers");
rtUnlockMutex(_mutex);
return;
}
for (unsigned int i = 0; i < _next_cvar; ++i) {
if (_vars[i].cvar == cvar) {
rt_cvar_change_event_list *entry = &_change_event_handlers[_next_event_handler++];
entry->Handler = event_handler;
entry->next = _vars[i].change_event_handlers;
entry->userdata = userdata;
_vars[i].change_event_handlers = entry;
}
}
rtUnlockMutex(_mutex);
}
RT_DLLEXPORT void rtNotifyCVARChange(const rt_cvar *cvar) {
#ifndef RT_DONT_LOG_CVAR_CHANGES
switch (cvar->type) {
case RT_CVAR_TYPE_INT:
rtLog("CVAR", "Changed %s to %d.", cvar->name, cvar->i);
break;
case RT_CVAR_TYPE_FLOAT:
rtLog("CVAR", "Changed %s to %f.", cvar->name, cvar->f);
break;
case RT_CVAR_TYPE_STRING:
rtLog("CVAR", "Changed %s to '%s'.", cvar->name, cvar->s);
break;
case RT_CVAR_TYPE_SIZE:
rtLog("CVAR", "Changed %s to %zu.", cvar->name, cvar->sz);
break;
default:
rtLog("CVAR", "Changed %s, but the cvar has an invalid type.", cvar->name);
break;
}
#endif
RT_VERIFY(_mutex);
rtLockMutex(_mutex);
for (unsigned int i = 0; i < _next_cvar; ++i) {
if (_vars[i].cvar == cvar) {
rt_cvar_change_event_list *entry = _vars[i].change_event_handlers;
rtUnlockMutex(_mutex);
while (entry) {
/* We don't want to hold the mutex while the event handler runs,
* because that would prevent any calls to CVar functions. */
entry->Handler(_vars[i].cvar, entry->userdata);
rtLockMutex(_mutex);
entry = entry->next;
rtUnlockMutex(_mutex);
}
return;
}
}
/* Not found... */
rtUnlockMutex(_mutex);
}
static int Handler(void *user, const char *section, const char *name, const char *value) {
/* We currently ignore sections. They could be used to categorize variables by system? */
RT_UNUSED(section);
const char *file_path = user;
rt_cvar *cvar = rtGetCVAR(name);
if (!cvar) {
/* Not a critical error. */
rtLog("CVAR", "Unknown CVar %s in config file %s.", name, file_path);
return 1;
}
int num_read = 0;
switch (cvar->type) {
case RT_CVAR_TYPE_INT:
num_read = sscanf(value, "%d", &cvar->i);
break;
case RT_CVAR_TYPE_FLOAT:
num_read = sscanf(value, "%f", &cvar->f);
break;
case RT_CVAR_TYPE_STRING: {
num_read = 1;
char *copy = rtStoreString(value);
if (!copy) {
rtReportError("CVAR",
"Failed to store string value of cvar %s in config file %s.",
name,
file_path);
return 0;
}
cvar->s = copy;
break;
}
case RT_CVAR_TYPE_SIZE:
num_read = sscanf(value, "%zu", &cvar->sz);
break;
default:
rtReportError("CVAR", "CVar %s has an invalid type.", cvar->name);
return 0;
}
if (num_read == 1) {
rtNotifyCVARChange(cvar);
} else {
rtLog("CVAR", "Failed to read value of CVar %s in config file %s.", cvar->name, file_path);
}
return 1;
}
/* Config files are ini files */
static rt_result ProcessConfigFile(const void *buffer, size_t fsz, const char *file_path) {
rt_result result = RT_SUCCESS;
if (ini_parse_string(buffer, Handler, (void *)file_path) < 0) {
rtLog("CVAR", "Failed to parse config file '%s'.", file_path);
return RT_UNKNOWN_ERROR;
}
return result;
}
RT_DLLEXPORT rt_result rtProcessConfigFiles(unsigned int count, const rt_file_id *fids) {
if (count > RT_LOAD_BATCH_MAX_SIZE) {
rtReportError("CVAR",
"Got %u config files, but the maximum number of config files is %u.",
count,
RT_LOAD_BATCH_MAX_SIZE);
return RT_INVALID_VALUE;
}
typedef struct {
void *buffer;
size_t fsz;
const char *path;
} config_file_buf;
rt_temp_arena temp = rtGetTemporaryArena(NULL, 0);
if (!temp.arena)
return RT_OUT_OF_MEMORY;
config_file_buf *configs = RT_ARENA_PUSH_ARRAY_ZERO(temp.arena, config_file_buf, count);
if (!configs) {
rtReturnTemporaryArena(temp);
return RT_OUT_OF_MEMORY;
}
rt_aio_handle *aios = RT_ARENA_PUSH_ARRAY_ZERO(temp.arena, rt_aio_handle, count);
if (!configs) {
rtReturnTemporaryArena(temp);
return RT_OUT_OF_MEMORY;
}
rt_result res = RT_SUCCESS;
rt_load_batch load_batch;
load_batch.num_loads = count;
for (unsigned int i = 0; i < count; ++i) {
const char *path = rtGetFilePath(fids[i]);
if (!path) {
rtReportError("CVAR", "Invalid config file path!");
res = RT_INVALID_VALUE;
goto out;
}
size_t fsz = rtGetFileSize(path);
load_batch.loads[i].dest = rtAllocBuffer(fsz);
if (!load_batch.loads[i].dest) {
rtReportError("CVAR", "Failed to allocate memory for config file.");
res = RT_OUT_OF_MEMORY;
goto out;
}
load_batch.loads[i].num_bytes = fsz;
load_batch.loads[i].offset = 0;
load_batch.loads[i].file = fids[i];
configs[i].buffer = load_batch.loads[i].dest;
configs[i].fsz = fsz;
configs[i].path = path;
}
if ((res = rtSubmitLoadBatch(&load_batch, aios)) != RT_SUCCESS) {
goto out;
}
for (unsigned int i = 0; i < count; ++i) {
rt_aio_state state = rtWaitForAIOCompletion(aios[i]);
if (state == RT_AIO_STATE_FINISHED) {
res = ProcessConfigFile(configs[i].buffer, configs[i].fsz, configs[i].path);
if (res != RT_SUCCESS)
goto out;
} else {
RT_ASSERT(state == RT_AIO_STATE_FAILED, "Unexpected aio state.");
res = RT_UNKNOWN_ERROR;
goto out;
}
}
out:
for (unsigned int i = 0; i < count; ++i) {
if (configs[i].buffer)
rtReleaseBuffer(configs[i].buffer, configs[i].fsz);
if (aios[i] != RT_AIO_INVALID_HANDLE)
rtReleaseAIO(aios[i]);
}
rtReturnTemporaryArena(temp);
return res;
}
/* This gets called before _any_ engine systems are available (especially AIO, ftab and
* Buffermanager) */
void ProcessEarlyEngineConfigs(void) {
const char *engine_cfgs[] = {"cfg/engine_early.cfg"};
void *buf = NULL;
size_t buf_sz = 0;
for (unsigned int i = 0; i < RT_ARRAY_COUNT(engine_cfgs); ++i) {
if (!rtDoesFileExist(engine_cfgs[i])) {
rtLog("CVAR", "Could not find early engine config file %s", engine_cfgs[i]);
continue;
}
size_t fsz = rtGetFileSize(engine_cfgs[i]);
if ((fsz + 1) > buf_sz) {
buf = realloc(buf, fsz + 1);
buf_sz = fsz + 1;
}
if (!rtSyncReadWholeFile(engine_cfgs[i], buf, buf_sz)) {
rtLog("CVAR", "Failed to read early engine config file %s", engine_cfgs[i]);
continue;
}
((char *)buf)[fsz] = '\0';
if (ProcessConfigFile(buf, fsz, engine_cfgs[i]) != RT_SUCCESS) {
rtLog("CVAR", "Failed to process early engine config file %s", engine_cfgs[i]);
}
}
free(buf);
}