taisei/src/cli.c

204 lines
4.9 KiB
C
Raw Normal View History

2017-09-12 03:28:15 +02:00
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
2017-09-12 03:28:15 +02:00
*/
#include "taisei.h"
2017-04-02 10:16:52 +02:00
#include <getopt.h>
2019-03-18 05:41:12 +01:00
#include "cli.h"
2017-04-02 10:16:52 +02:00
#include "difficulty.h"
#include "util.h"
#include "log.h"
#include "stage.h"
#include "plrmodes.h"
struct TsOption { struct option opt; const char *help; const char *argname;};
static void print_help(struct TsOption* opts) {
tsfprintf(stdout, "Usage: taisei [OPTIONS]\nTaisei is an open source Touhou clone.\n\nOptions:\n");
int margin = 20;
for(struct TsOption *opt = opts; opt->opt.name; opt++) {
2017-04-03 01:10:16 +02:00
tsfprintf(stdout, " -%c, --%s ", opt->opt.val,opt->opt.name);
2017-04-02 10:16:52 +02:00
int length = margin-(int)strlen(opt->opt.name);
if(opt->argname) {
2017-04-03 01:10:16 +02:00
tsfprintf(stdout, "%s", opt->argname);
2017-04-02 10:16:52 +02:00
length -= (int)strlen(opt->argname);
}
for(int i = 0; i < length; i++)
tsfprintf(stdout, " ");
if(opt->argname)
tsfprintf(stdout, opt->help, opt->argname);
else
2017-04-03 01:10:16 +02:00
tsfprintf(stdout, "%s", opt->help);
tsfprintf(stdout, "\n");
2017-04-02 10:16:52 +02:00
}
}
int cli_args(int argc, char **argv, CLIAction *a) {
struct TsOption taisei_opts[] = {
{{"replay", required_argument, 0, 'r'}, "Play a replay from %s", "FILE"},
{{"verify-replay", required_argument, 0, 'R'}, "Play a replay from %s in headless mode, crash as soon as it desyncs", "FILE"},
2017-04-02 10:16:52 +02:00
#ifdef DEBUG
{{"play", no_argument, 0, 'p'}, "Play a specific stage", 0},
{{"sid", required_argument, 0, 'i'}, "Select stage by %s", "ID"},
{{"diff", required_argument, 0, 'd'}, "Select a difficulty (Easy/Normal/Hard/Lunatic)", "DIFF"},
{{"shotmode", required_argument, 0, 's'}, "Select a shotmode (marisaA/youmuA/marisaB/youmuB)", "SMODE"},
{{"dumpstages", no_argument, 0, 'u'}, "Print a list of all stages in the game", 0},
2017-04-18 21:48:18 +02:00
{{"vfs-tree", required_argument, 0, 't'}, "Print the virtual filesystem tree starting from %s", "PATH"},
2017-04-02 10:16:52 +02:00
#endif
{{"frameskip", optional_argument, 0, 'f'}, "Disable FPS limiter, render only every %s frame", "FRAME"},
2017-10-23 12:48:30 +02:00
{{"credits", no_argument, 0, 'c'}, "Show the credits scene and exit"},
{{"help", no_argument, 0, 'h'}, "Display this help"},
2017-04-02 10:16:52 +02:00
{{0,0,0,0},0,0}
};
memset(a,0,sizeof(CLIAction));
int nopts = sizeof(taisei_opts)/sizeof(taisei_opts[0]);
struct option opts[nopts];
char optc[2*nopts+1];
char *ptr = optc;
for(int i = 0; i < nopts; i++) {
opts[i] = taisei_opts[i].opt;
*ptr = opts[i].val;
ptr++;
2017-04-02 10:16:52 +02:00
if(opts[i].has_arg != no_argument) {
*ptr = ':';
ptr++;
if(opts[i].has_arg == optional_argument) {
*ptr = ':';
ptr++;
}
2017-04-02 10:16:52 +02:00
}
}
*ptr = 0;
// on OS X, programs get passed some strange parameter when they are run from bundles.
for(int i = 0; i < argc; i++) {
if(strstartswith(argv[i],"-psn_"))
argv[i][0] = 0;
}
2017-04-02 10:16:52 +02:00
int c;
2017-04-04 11:10:54 +02:00
uint16_t stageid = 0;
PlayerMode *plrmode = NULL;
2017-04-03 01:10:16 +02:00
while((c = getopt_long(argc, argv, optc, opts, 0)) != -1) {
char *endptr = NULL;
2017-04-02 10:16:52 +02:00
switch(c) {
case 'h':
case '?':
print_help(taisei_opts);
2017-04-03 01:10:16 +02:00
// a->type = CLI_Quit;
exit(1);
2017-04-02 10:16:52 +02:00
break;
case 'r':
a->type = CLI_PlayReplay;
a->filename = strdup(optarg);
break;
case 'R':
a->type = CLI_VerifyReplay;
a->filename = strdup(optarg);
break;
2017-04-02 10:16:52 +02:00
case 'p':
a->type = CLI_SelectStage;
break;
case 'i':
stageid = strtol(optarg, &endptr, 16);
if(!*optarg || endptr == optarg)
log_fatal("Stage id '%s' is not a number", optarg);
2017-04-02 10:16:52 +02:00
break;
case 'u':
a->type = CLI_DumpStages;
break;
case 'd':
2017-04-03 01:10:16 +02:00
a->diff = D_Any;
2017-04-02 10:16:52 +02:00
for(int i = D_Easy ; i <= NUM_SELECTABLE_DIFFICULTIES; i++) {
2017-04-03 01:10:16 +02:00
if(strcasecmp(optarg, difficulty_name(i)) == 0) {
2017-04-02 10:16:52 +02:00
a->diff = i;
break;
}
}
2017-04-03 01:10:16 +02:00
if(a->diff == D_Any) {
log_fatal("Invalid difficulty '%s'", optarg);
}
2017-04-02 10:16:52 +02:00
break;
case 's':
if(!(plrmode = plrmode_parse(optarg)))
log_fatal("Invalid shotmode '%s'", optarg);
2017-04-02 10:16:52 +02:00
break;
case 'f':
a->frameskip = 1;
if(optarg) {
a->frameskip = strtol(optarg, &endptr, 10);
if(endptr == optarg) {
log_fatal("Frameskip value '%s' is not a number", optarg);
}
if(a->frameskip < 0) {
a->frameskip = INT_MAX;
}
}
break;
2017-04-18 21:48:18 +02:00
case 't':
a->type = CLI_DumpVFSTree,
a->filename = strdup(optarg ? optarg : "");
break;
2017-10-23 12:48:30 +02:00
case 'c':
a->type = CLI_Credits;
break;
2017-04-02 10:16:52 +02:00
default:
log_fatal("Unknown option (this shouldnt happen)");
}
}
2017-04-04 11:10:54 +02:00
if(stageid) {
switch(a->type) {
case CLI_PlayReplay:
case CLI_VerifyReplay:
case CLI_SelectStage:
if(stage_get(stageid) == NULL) {
log_fatal("Invalid stage id: %X", stageid);
}
break;
default:
log_warn("--sid was ignored");
break;
2017-04-03 01:50:48 +02:00
}
}
2017-04-02 10:16:52 +02:00
if(plrmode) {
if(a->type == CLI_SelectStage) {
a->plrmode = plrmode;
} else {
log_warn("--shotmode was ignored");
}
2017-04-02 10:16:52 +02:00
}
a->stageid = stageid;
2017-04-04 14:30:32 +02:00
if(a->type == CLI_SelectStage && !stageid)
2017-04-02 10:16:52 +02:00
log_fatal("StageSelect mode, but no stage id was given");
return 0;
}
void free_cli_action(CLIAction *a) {
free(a->filename);
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
a->filename = NULL;
2017-04-02 10:16:52 +02:00
}