304 lines
9.5 KiB
C
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);
|
|
} |