#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 #include #include #include #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); }