2012-08-03 07:06:19 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2017-02-11 02:24:47 +01:00
|
|
|
* See COPYING for further information.
|
2012-08-03 07:06:19 +02:00
|
|
|
* ---
|
2024-05-16 23:30:41 +02:00
|
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
2012-08-03 07:06:19 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
#include "bitarray.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "gamepad.h"
|
|
|
|
#include "global.h" // IWYU pragma: keep
|
|
|
|
#include "util/kvparser.h"
|
|
|
|
#include "util/strbuf.h"
|
|
|
|
#include "version.h"
|
|
|
|
#include "vfs/public.h"
|
2012-08-03 07:06:19 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
#define CONFIG_FILE "storage/config"
|
2017-02-17 17:03:49 +01:00
|
|
|
|
|
|
|
CONFIGDEFS_EXPORT ConfigEntry configdefs[] = {
|
2018-07-04 10:36:16 +02:00
|
|
|
#define CONFIGDEF(type,entryname,default,ufield) { type, entryname, { .ufield = default } },
|
2017-02-17 17:03:49 +01:00
|
|
|
#define CONFIGDEF_KEYBINDING(id,entryname,default) CONFIGDEF(CONFIG_TYPE_KEYBINDING, entryname, default, i)
|
2017-07-15 05:22:56 +02:00
|
|
|
#define CONFIGDEF_GPKEYBINDING(id,entryname,default) CONFIGDEF(CONFIG_TYPE_GPKEYBINDING, entryname, default, i)
|
2017-02-17 17:03:49 +01:00
|
|
|
#define CONFIGDEF_INT(id,entryname,default) CONFIGDEF(CONFIG_TYPE_INT, entryname, default, i)
|
|
|
|
#define CONFIGDEF_FLOAT(id,entryname,default) CONFIGDEF(CONFIG_TYPE_FLOAT, entryname, default, f)
|
|
|
|
#define CONFIGDEF_STRING(id,entryname,default) CONFIGDEF(CONFIG_TYPE_STRING, entryname, NULL, s)
|
|
|
|
|
|
|
|
CONFIGDEFS
|
|
|
|
{0}
|
|
|
|
|
|
|
|
#undef CONFIGDEF
|
|
|
|
#undef CONFIGDEF_KEYBINDING
|
|
|
|
#undef CONFIGDEF_GPKEYBINDING
|
|
|
|
#undef CONFIGDEF_INT
|
|
|
|
#undef CONFIGDEF_FLOAT
|
|
|
|
#undef CONFIGDEF_STRING
|
2012-08-03 07:06:19 +02:00
|
|
|
};
|
|
|
|
|
2017-02-18 05:18:13 +01:00
|
|
|
typedef struct ConfigEntryList {
|
2017-12-24 07:16:25 +01:00
|
|
|
LIST_INTERFACE(struct ConfigEntryList);
|
2017-02-18 05:18:13 +01:00
|
|
|
ConfigEntry entry;
|
|
|
|
} ConfigEntryList;
|
|
|
|
|
|
|
|
static ConfigEntryList *unknowndefs = NULL;
|
|
|
|
|
|
|
|
static void config_delete_unknown_entries(void);
|
|
|
|
|
2018-07-04 10:36:16 +02:00
|
|
|
static void config_copy_value(ConfigEntryType type, ConfigValue *dst, ConfigValue src) {
|
|
|
|
switch(type) {
|
|
|
|
case CONFIG_TYPE_INT:
|
|
|
|
case CONFIG_TYPE_KEYBINDING:
|
|
|
|
case CONFIG_TYPE_GPKEYBINDING:
|
|
|
|
dst->i = src.i;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
dst->f = src.f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_STRING:
|
|
|
|
stralloc(&dst->s, src.s);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
UNREACHABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int config_compare_values(ConfigEntryType type, ConfigValue val0, ConfigValue val1) {
|
|
|
|
switch(type) {
|
|
|
|
case CONFIG_TYPE_INT:
|
|
|
|
case CONFIG_TYPE_KEYBINDING:
|
|
|
|
case CONFIG_TYPE_GPKEYBINDING:
|
|
|
|
return (val0.i > val1.i) - (val0.i < val1.i);
|
|
|
|
|
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
return (val0.f > val1.f) - (val0.f < val1.f);
|
|
|
|
|
|
|
|
case CONFIG_TYPE_STRING:
|
2024-05-03 02:39:51 +02:00
|
|
|
return val0.s
|
|
|
|
? (val1.s ? strcmp(val0.s, val1.s) : -1)
|
|
|
|
: (val1.s ? 1 : 0);
|
2018-07-04 10:36:16 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
UNREACHABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void config_free_value(ConfigEntryType type, ConfigValue *val) {
|
|
|
|
switch(type) {
|
|
|
|
case CONFIG_TYPE_STRING:
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(val->s);
|
2018-07-04 10:36:16 +02:00
|
|
|
val->s = NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-04 15:15:35 +01:00
|
|
|
void config_shutdown(void) {
|
2017-02-17 17:03:49 +01:00
|
|
|
for(ConfigEntry *e = configdefs; e->name; ++e) {
|
2018-07-04 10:36:16 +02:00
|
|
|
config_free_value(e->type, &e->val);
|
2017-02-17 17:03:49 +01:00
|
|
|
}
|
2017-02-18 05:18:13 +01:00
|
|
|
|
|
|
|
config_delete_unknown_entries();
|
2017-02-17 17:03:49 +01:00
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
#ifndef CONFIG_RAWACCESS
|
|
|
|
ConfigEntry* config_get(ConfigIndex idx) {
|
|
|
|
assert(idx >= 0 && idx < CONFIGIDX_NUM);
|
|
|
|
return configdefs + idx;
|
|
|
|
}
|
|
|
|
#endif
|
2017-02-10 02:11:45 +01:00
|
|
|
|
2017-02-18 05:18:13 +01:00
|
|
|
static ConfigEntry* config_find_entry(const char *name) {
|
2017-02-17 17:03:49 +01:00
|
|
|
ConfigEntry *e = configdefs;
|
|
|
|
do if(!strcmp(e->name, name)) return e; while((++e)->name);
|
|
|
|
return NULL;
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
KeyIndex config_key_from_scancode(int scan) {
|
|
|
|
for(int i = CONFIG_KEY_FIRST; i <= CONFIG_KEY_LAST; ++i) {
|
|
|
|
if(configdefs[i].val.i == scan) {
|
|
|
|
return CFGIDX_TO_KEYIDX(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-03 07:06:19 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
GamepadKeyIndex config_gamepad_key_from_gamepad_button(int btn) {
|
|
|
|
for(int i = CONFIG_GAMEPAD_KEY_FIRST; i <= CONFIG_GAMEPAD_KEY_LAST; ++i) {
|
|
|
|
if(configdefs[i].val.i == btn) {
|
|
|
|
return CFGIDX_TO_GPKEYIDX(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-15 02:41:21 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
KeyIndex config_key_from_gamepad_key(GamepadKeyIndex gpkey) {
|
|
|
|
#define CONFIGDEF_GPKEYBINDING(id,entryname,default) case GAMEPAD_##id: return id;
|
2012-08-15 02:41:21 +02:00
|
|
|
switch(gpkey) {
|
2017-02-17 17:03:49 +01:00
|
|
|
GPKEYDEFS
|
|
|
|
default: return -1;
|
2012-08-15 02:41:21 +02:00
|
|
|
}
|
2017-02-17 17:03:49 +01:00
|
|
|
#undef CONFIGDEF_GPKEYBINDING
|
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
GamepadKeyIndex config_gamepad_key_from_key(KeyIndex key) {
|
|
|
|
#define CONFIGDEF_GPKEYBINDING(id,entryname,default) case id: return GAMEPAD_##id;
|
|
|
|
switch(key) {
|
|
|
|
GPKEYDEFS
|
|
|
|
default: return -1;
|
|
|
|
}
|
|
|
|
#undef CONFIGDEF_GPKEYBINDING
|
2012-08-15 02:41:21 +02:00
|
|
|
}
|
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
KeyIndex config_key_from_gamepad_button(int btn) {
|
|
|
|
return config_key_from_gamepad_key(config_gamepad_key_from_gamepad_button(btn));
|
2012-08-15 02:41:21 +02:00
|
|
|
}
|
|
|
|
|
2023-02-23 01:48:16 +01:00
|
|
|
static void config_dump_stringval(StringBuffer *buf, ConfigEntryType etype, ConfigValue *val) {
|
|
|
|
switch(etype) {
|
|
|
|
case CONFIG_TYPE_INT:
|
|
|
|
strbuf_printf(buf, "%i", val->i);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_KEYBINDING:
|
|
|
|
strbuf_cat(buf, SDL_GetScancodeName(val->i));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_GPKEYBINDING:
|
|
|
|
strbuf_cat(buf, gamepad_button_name(val->i));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_STRING:
|
2024-05-03 02:39:51 +02:00
|
|
|
strbuf_cat(buf, val->s ?: "(null)");
|
2023-02-23 01:48:16 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
strbuf_printf(buf, "%f", val->f);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: UNREACHABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 19:23:22 +01:00
|
|
|
static void config_set_val(ConfigIndex idx, ConfigValue v) {
|
|
|
|
ConfigEntry *e = config_get(idx);
|
|
|
|
|
2018-07-04 10:36:16 +02:00
|
|
|
if(!config_compare_values(e->type, e->val, v)) {
|
2017-02-17 19:23:22 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-04 10:36:16 +02:00
|
|
|
ConfigValue oldv = { 0 };
|
2021-06-18 15:11:00 +02:00
|
|
|
ConfigEntryType ctype = e->type;
|
|
|
|
config_copy_value(ctype, &oldv, e->val);
|
|
|
|
config_copy_value(ctype, &e->val, v);
|
2023-02-23 01:49:00 +01:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
StringBuffer sb = {};
|
|
|
|
strbuf_printf(&sb, "%s: [", e->name);
|
|
|
|
config_dump_stringval(&sb, ctype, &oldv);
|
|
|
|
strbuf_printf(&sb, "] -> [");
|
|
|
|
config_dump_stringval(&sb, ctype, &e->val);
|
|
|
|
strbuf_printf(&sb, "]");
|
|
|
|
log_debug("%s", sb.start);
|
|
|
|
strbuf_free(&sb);
|
|
|
|
#endif
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
if(SDL_WasInit(SDL_INIT_EVENTS)) {
|
|
|
|
events_emit(TE_CONFIG_UPDATED, idx, &e->val, &oldv);
|
|
|
|
}
|
|
|
|
|
2021-06-18 15:11:00 +02:00
|
|
|
config_free_value(ctype, &oldv);
|
2017-02-17 19:23:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int config_set_int(ConfigIndex idx, int val) {
|
|
|
|
ConfigValue v = {.i = val};
|
|
|
|
config_set_val(idx, v);
|
|
|
|
return config_get_int(idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
double config_set_float(ConfigIndex idx, double val) {
|
|
|
|
ConfigValue v = {.f = val};
|
|
|
|
config_set_val(idx, v);
|
|
|
|
return config_get_float(idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
char* config_set_str(ConfigIndex idx, const char *val) {
|
|
|
|
ConfigValue v = {.s = (char*)val};
|
|
|
|
config_set_val(idx, v);
|
|
|
|
return config_get_str(idx);
|
|
|
|
}
|
|
|
|
|
2017-02-18 05:18:13 +01:00
|
|
|
static ConfigEntry* config_get_unknown_entry(const char *name) {
|
|
|
|
ConfigEntry *e;
|
|
|
|
ConfigEntryList *l;
|
|
|
|
|
|
|
|
for(l = unknowndefs; l; l = l->next) {
|
|
|
|
if(!strcmp(name, l->entry.name)) {
|
|
|
|
return &l->entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
l = ALLOC(typeof(*l));
|
2017-02-18 05:18:13 +01:00
|
|
|
e = &l->entry;
|
|
|
|
stralloc(&e->name, name);
|
|
|
|
e->type = CONFIG_TYPE_STRING;
|
2017-12-24 07:16:25 +01:00
|
|
|
list_push(&unknowndefs, l);
|
2017-02-18 05:18:13 +01:00
|
|
|
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void config_set_unknown(const char *name, const char *val) {
|
|
|
|
stralloc(&config_get_unknown_entry(name)->val.s, val);
|
|
|
|
}
|
|
|
|
|
2017-11-21 15:45:01 +01:00
|
|
|
static void* config_delete_unknown_entry(List **list, List *lentry, void *arg) {
|
2017-02-18 05:18:13 +01:00
|
|
|
ConfigEntry *e = &((ConfigEntryList*)lentry)->entry;
|
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(e->name);
|
|
|
|
mem_free(e->val.s);
|
|
|
|
mem_free(list_unlink(list, lentry));
|
2017-02-18 05:18:13 +01:00
|
|
|
|
2017-11-21 15:45:01 +01:00
|
|
|
return NULL;
|
2017-02-18 05:18:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void config_delete_unknown_entries(void) {
|
2017-12-24 07:16:25 +01:00
|
|
|
list_foreach(&unknowndefs, config_delete_unknown_entry, NULL);
|
2017-02-18 05:18:13 +01:00
|
|
|
}
|
|
|
|
|
2017-04-18 21:48:18 +02:00
|
|
|
void config_save(void) {
|
2017-09-26 23:55:28 +02:00
|
|
|
SDL_RWops *out = vfs_open(CONFIG_FILE, VFS_MODE_WRITE);
|
2012-08-03 07:06:19 +02:00
|
|
|
ConfigEntry *e = configdefs;
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-09-26 23:55:28 +02:00
|
|
|
if(!out) {
|
2019-02-14 22:11:27 +01:00
|
|
|
log_error("VFS error: %s", vfs_get_error());
|
2012-08-03 07:06:19 +02:00
|
|
|
return;
|
2017-09-26 23:55:28 +02:00
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2023-02-23 01:48:16 +01:00
|
|
|
StringBuffer sbuf = {};
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2023-02-23 01:48:16 +01:00
|
|
|
strbuf_printf(&sbuf, "# Generated by %s %s", TAISEI_VERSION_FULL, TAISEI_VERSION_BUILD_TYPE);
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2023-02-23 01:48:16 +01:00
|
|
|
do {
|
|
|
|
strbuf_printf(&sbuf, "\n%s = ", e->name);
|
|
|
|
config_dump_stringval(&sbuf, e->type, &e->val);
|
2012-08-03 07:06:19 +02:00
|
|
|
} while((++e)->name);
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-02-18 05:18:13 +01:00
|
|
|
if(unknowndefs) {
|
2023-02-23 01:48:16 +01:00
|
|
|
strbuf_cat(&sbuf, "\n# The following options were not recognized by taisei");
|
2017-02-18 05:18:13 +01:00
|
|
|
|
|
|
|
for(ConfigEntryList *l = unknowndefs; l; l = l->next) {
|
|
|
|
e = &l->entry;
|
2023-02-23 01:48:16 +01:00
|
|
|
strbuf_printf(&sbuf, "\n%s = %s", e->name, e->val.s);
|
2017-02-18 05:18:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-23 01:48:16 +01:00
|
|
|
SDL_RWwrite(out, sbuf.start, sbuf.pos - sbuf.start, 1);
|
|
|
|
strbuf_free(&sbuf);
|
2017-03-12 22:40:19 +01:00
|
|
|
SDL_RWclose(out);
|
2017-04-18 21:48:18 +02:00
|
|
|
|
2017-04-27 11:10:49 +02:00
|
|
|
char *sp = vfs_repr(CONFIG_FILE, true);
|
2017-04-18 21:48:18 +02:00
|
|
|
log_info("Saved config '%s'", sp);
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(sp);
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
|
|
|
|
2012-08-15 17:03:53 +02:00
|
|
|
#define INTOF(s) ((int)strtol(s, NULL, 10))
|
|
|
|
#define FLOATOF(s) ((float)strtod(s, NULL))
|
2012-08-03 07:06:19 +02:00
|
|
|
|
2017-09-26 23:55:28 +02:00
|
|
|
typedef struct ConfigParseState {
|
2024-05-03 02:39:51 +02:00
|
|
|
BIT_ARRAY(CONFIGIDX_NUM) touched_settings;
|
2017-09-26 23:55:28 +02:00
|
|
|
} ConfigParseState;
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
static bool config_set(const char *key, const char *val, void *data) {
|
2017-02-17 17:03:49 +01:00
|
|
|
ConfigEntry *e = config_find_entry(key);
|
2017-09-26 23:55:28 +02:00
|
|
|
ConfigParseState *state = data;
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2012-08-03 07:06:19 +02:00
|
|
|
if(!e) {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_warn("Unknown setting '%s'", key);
|
2017-02-18 05:18:13 +01:00
|
|
|
config_set_unknown(key, val);
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
return true;
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
ConfigIndex idx = e - configdefs;
|
|
|
|
assert((uintptr_t)idx < CONFIGIDX_NUM);
|
|
|
|
bitarray_set(&state->touched_settings, idx, true);
|
|
|
|
|
|
|
|
bool error = false;
|
|
|
|
ConfigValue cval = { };
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2012-08-03 07:06:19 +02:00
|
|
|
switch(e->type) {
|
2017-02-17 17:03:49 +01:00
|
|
|
case CONFIG_TYPE_INT:
|
2024-05-03 02:39:51 +02:00
|
|
|
cval.i = INTOF(val);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
cval.f = FLOATOF(val);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFIG_TYPE_STRING:
|
|
|
|
cval.s = (char*)val;
|
2012-08-03 07:06:19 +02:00
|
|
|
break;
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-02-17 17:03:49 +01:00
|
|
|
case CONFIG_TYPE_KEYBINDING: {
|
2017-02-07 05:26:04 +01:00
|
|
|
SDL_Scancode scan = SDL_GetScancodeFromName(val);
|
2017-02-04 05:30:37 +01:00
|
|
|
|
2017-02-07 05:26:04 +01:00
|
|
|
if(scan == SDL_SCANCODE_UNKNOWN) {
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
log_warn("Unknown key '%s'", val);
|
2024-05-03 02:39:51 +02:00
|
|
|
error = true;
|
2017-02-04 05:30:37 +01:00
|
|
|
} else {
|
2024-05-03 02:39:51 +02:00
|
|
|
cval.i = scan;
|
2017-02-04 05:30:37 +01:00
|
|
|
}
|
|
|
|
|
2012-08-03 07:06:19 +02:00
|
|
|
break;
|
2017-02-04 05:30:37 +01:00
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2017-07-15 05:22:56 +02:00
|
|
|
case CONFIG_TYPE_GPKEYBINDING: {
|
2017-12-30 16:38:35 +01:00
|
|
|
GamepadButton btn = gamepad_button_from_name(val);
|
2017-07-15 05:22:56 +02:00
|
|
|
|
2017-12-30 16:38:35 +01:00
|
|
|
if(btn == GAMEPAD_BUTTON_INVALID) {
|
|
|
|
log_warn("Unknown gamepad button '%s'", val);
|
2024-05-03 02:39:51 +02:00
|
|
|
error = true;
|
2017-07-15 05:22:56 +02:00
|
|
|
} else {
|
2024-05-03 02:39:51 +02:00
|
|
|
cval.i = btn;
|
2017-07-15 05:22:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
default: UNREACHABLE;
|
|
|
|
}
|
2017-02-11 02:24:47 +01:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
if(!error) {
|
|
|
|
config_set_val(idx, cval);
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
2018-04-12 16:08:48 +02:00
|
|
|
|
|
|
|
return true;
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#undef INTOF
|
2012-08-15 17:03:53 +02:00
|
|
|
#undef FLOATOF
|
2012-08-03 07:06:19 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
typedef void (*ConfigUpgradeFunc)(ConfigParseState *state);
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static void config_upgrade_1(ConfigParseState *state) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
// reset vsync to the default value
|
2024-05-03 02:39:51 +02:00
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_VSYNC)) {
|
|
|
|
config_set_int(CONFIG_VSYNC, CONFIG_VSYNC_DEFAULT);
|
|
|
|
}
|
2017-09-26 23:55:28 +02:00
|
|
|
|
|
|
|
// this version also changes meaning of the vsync value
|
2020-04-02 18:40:55 +02:00
|
|
|
// previously it was: 0 = on, 1 = off, 2 = adaptive, because mia doesn't know how my absolutely genius options menu works.
|
2017-09-26 23:55:28 +02:00
|
|
|
// now it is: 0 = off, 1 = on, 2 = adaptive, as it should be.
|
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
#ifdef __EMSCRIPTEN__
|
2024-05-03 02:39:51 +02:00
|
|
|
static void config_upgrade_2(ConfigParseState *state) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
// emscripten defaults for these have been changed
|
2024-05-03 02:39:51 +02:00
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_VSYNC)) {
|
|
|
|
config_set_int(CONFIG_VSYNC, CONFIG_VSYNC_DEFAULT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_FXAA)) {
|
|
|
|
config_set_int(CONFIG_FXAA, CONFIG_FXAA_DEFAULT);
|
|
|
|
}
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define config_upgrade_2 NULL
|
|
|
|
#endif
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static void config_upgrade_3(ConfigParseState *state) {
|
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_MIXER_CHUNKSIZE)) {
|
|
|
|
config_set_int(CONFIG_MIXER_CHUNKSIZE, CONFIG_CHUNKSIZE_DEFAULT);
|
|
|
|
}
|
2023-06-08 04:34:54 +02:00
|
|
|
}
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static void config_upgrade_4(ConfigParseState *state) {
|
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_GAMEPAD_BTNREPEAT_DELAY)) {
|
|
|
|
config_set_float(CONFIG_GAMEPAD_BTNREPEAT_DELAY, CONFIG_GAMEPAD_BTNREPEAT_DELAY_DEFAULT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bitarray_get(&state->touched_settings, CONFIG_GAMEPAD_BTNREPEAT_INTERVAL)) {
|
|
|
|
config_set_float(CONFIG_GAMEPAD_BTNREPEAT_INTERVAL, CONFIG_GAMEPAD_BTNREPEAT_INTERVAL_DEFAULT);
|
|
|
|
}
|
2023-07-11 23:51:44 +02:00
|
|
|
}
|
|
|
|
|
2017-09-26 23:55:28 +02:00
|
|
|
static ConfigUpgradeFunc config_upgrades[] = {
|
|
|
|
/*
|
|
|
|
To bump the config version and add an upgrade state, simply append an upgrade function to this array.
|
|
|
|
NEVER reorder these entries, remove them, etc, ONLY append.
|
|
|
|
|
|
|
|
If a stage is no longer needed (nothing useful needs to be done, e.g. the later stages would override it),
|
|
|
|
then replace the function pointer with NULL.
|
|
|
|
*/
|
|
|
|
|
|
|
|
config_upgrade_1,
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
config_upgrade_2,
|
2023-06-08 04:34:54 +02:00
|
|
|
config_upgrade_3,
|
2023-07-11 23:51:44 +02:00
|
|
|
config_upgrade_4,
|
2017-09-26 23:55:28 +02:00
|
|
|
};
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static void config_set_version_latest(void) {
|
|
|
|
config_set_int(CONFIG_VERSION, sizeof(config_upgrades) / sizeof(ConfigUpgradeFunc));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void config_apply_upgrades(int start, ConfigParseState *state) {
|
2017-09-26 23:55:28 +02:00
|
|
|
int num_upgrades = sizeof(config_upgrades) / sizeof(ConfigUpgradeFunc);
|
|
|
|
|
|
|
|
if(start > num_upgrades) {
|
|
|
|
log_warn("Config seems to be from a newer version of Taisei (config version %i, ours is %i)", start, num_upgrades);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int upgrade = start; upgrade < num_upgrades; ++upgrade) {
|
|
|
|
if(config_upgrades[upgrade]) {
|
|
|
|
log_info("Upgrading config to version %i", upgrade + 1);
|
2024-05-03 02:39:51 +02:00
|
|
|
config_upgrades[upgrade](state);
|
2017-09-26 23:55:28 +02:00
|
|
|
} else {
|
|
|
|
log_debug("Upgrade to version %i is a no-op", upgrade + 1);
|
|
|
|
}
|
|
|
|
}
|
2024-05-03 02:39:51 +02:00
|
|
|
|
|
|
|
config_set_version_latest();
|
2017-09-26 23:55:28 +02:00
|
|
|
}
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static bool config_load_stream(SDL_RWops *stream) {
|
|
|
|
config_set_int(CONFIG_VERSION, 0);
|
|
|
|
ConfigParseState state = {};
|
2017-04-18 21:48:18 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
if(!parse_keyvalue_stream_cb(stream, config_set, &state)) {
|
|
|
|
log_warn("Errors occured while parsing the configuration file");
|
|
|
|
}
|
2017-04-18 21:48:18 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
config_apply_upgrades(config_get_int(CONFIG_VERSION), &state);
|
|
|
|
return true;
|
|
|
|
}
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
static bool config_load_file(const char *vfspath, LogLevel fail_loglevel) {
|
|
|
|
SDL_RWops *stream = vfs_open(vfspath, VFS_MODE_READ);
|
|
|
|
char *syspath_alloc = vfs_repr(vfspath, true);
|
|
|
|
const char *syspath = syspath_alloc ?: vfspath;
|
|
|
|
log_info("Loading configuration from %s", syspath);
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
if(UNLIKELY(!stream)) {
|
|
|
|
log_custom(fail_loglevel, "VFS error: %s", vfs_get_error());
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
if(vfs_query(vfspath).exists) {
|
|
|
|
log_error("Config file %s exists but is not readable", syspath);
|
2017-09-26 23:55:28 +02:00
|
|
|
}
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
mem_free(syspath_alloc);
|
|
|
|
return false;
|
2017-03-16 07:33:38 +01:00
|
|
|
}
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
mem_free(syspath_alloc);
|
|
|
|
bool ok = config_load_stream(stream);
|
|
|
|
SDL_RWclose(stream);
|
|
|
|
return ok;
|
|
|
|
}
|
2017-09-26 23:55:28 +02:00
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
void config_load(void) {
|
|
|
|
config_reset();
|
|
|
|
config_load_file(CONFIG_FILE, LOG_INFO);
|
2019-08-02 20:38:33 +02:00
|
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
config_set_int(CONFIG_GAMEPAD_ENABLED, true);
|
|
|
|
config_set_str(CONFIG_GAMEPAD_DEVICE, "any");
|
2023-10-10 18:31:23 +02:00
|
|
|
config_set_int(CONFIG_VID_WIDTH, nxGetInitialScreenWidth());
|
|
|
|
config_set_int(CONFIG_VID_HEIGHT, nxGetInitialScreenHeight());
|
2019-08-02 20:38:33 +02:00
|
|
|
#endif
|
2012-08-03 07:06:19 +02:00
|
|
|
}
|
2023-06-23 08:37:07 +02:00
|
|
|
|
|
|
|
void config_reset(void) {
|
|
|
|
#define CONFIGDEF(id, default, type) config_set_##type(CONFIG_##id, default);
|
|
|
|
#define CONFIGDEF_KEYBINDING(id, entryname, default) CONFIGDEF(id, default, int)
|
|
|
|
#define CONFIGDEF_GPKEYBINDING(id, entryname, default) CONFIGDEF(GAMEPAD_##id, default, int)
|
|
|
|
#define CONFIGDEF_INT(id, entryname, default) CONFIGDEF(id, default, int)
|
|
|
|
#define CONFIGDEF_FLOAT(id, entryname, default) CONFIGDEF(id, default, float)
|
|
|
|
#define CONFIGDEF_STRING(id, entryname, default) CONFIGDEF(id, default, str)
|
|
|
|
|
|
|
|
CONFIGDEFS
|
|
|
|
|
|
|
|
#undef CONFIGDEF
|
|
|
|
#undef CONFIGDEF_KEYBINDING
|
|
|
|
#undef CONFIGDEF_GPKEYBINDING
|
|
|
|
#undef CONFIGDEF_INT
|
|
|
|
#undef CONFIGDEF_FLOAT
|
|
|
|
#undef CONFIGDEF_STRING
|
|
|
|
|
2024-05-03 02:39:51 +02:00
|
|
|
config_set_version_latest();
|
|
|
|
config_load_file("res/config.default", LOG_INFO);
|
2023-06-23 08:37:07 +02:00
|
|
|
}
|