23#include <sys/resource.h>
24#ifdef ASE_WITH_CPPTRACE
25#include <cpptrace/from_current.hpp>
32#define MDEBUG(...) Ase::debug ("memory", __VA_ARGS__)
42MainAppImpl::MainAppImpl()
47static int arg_unauth_port = 0;
59print_usage (
bool help)
67 printout (
"Usage: %s [OPTIONS] [project.anklang]\n",
executable_name());
68 printout (
" --check Run integrity tests\n");
69 printout (
" --disable-randomization Test mode for deterministic tests\n");
70 printout (
" --fatal-warnings Abort on warnings and failing assertions\n");
71 printout (
" --help Print program usage and options\n");
72 printout (
" --jsbin Print Javascript IPC & binary messages\n");
73 printout (
" --jsipc Print Javascript IPC messages\n");
74 printout (
" --jsonts Print TypeScript bindings\n");
75 printout (
" --list-drivers Print PCM and MIDI drivers\n");
76 printout (
" --list-tests List all test names\n");
77 printout (
" --list-ui-tests List all TypeScript UI test function names\n");
78 printout (
" --norc Prevent loading of any rc files\n");
79 printout (
" --ui-test=test Specify TypeScript UI test(s) to run (comma-separated)\n");
80 printout (
" --ui-js=script Run JavaScript after UI startup (headless by default)\n");
81 printout (
" The window closes unless window.auto_close(false) is called\n");
82 printout (
" --play-autostart Automatically start playback of `project.anklang`\n");
83 printout (
" --rand64 Produce 64bit random numbers on stdout\n");
84 printout (
" --test[=test] Run specific test(s) (comma-separated)\n");
85 printout (
" --unauth-dev=NUM Open an unauthenticated websocket port for testing\n");
86 printout (
" --headless[=bool] Run browser headless (default for --ui-test and --ui-js)\n");
87 printout (
" --ui <none|chromium|google-chrome|htmlgui>\n");
88 printout (
" Open GUI in web browser [htmlgui]\n");
89 printout (
" --version Print program version\n");
90 printout (
" -M mididriver Force use of <mididriver>\n");
91 printout (
" -P pcmdriver Force use of <pcmdriver>\n");
92 printout (
" -o wavfile Capture output to OPUS/FLAC/WAV file\n");
93 printout (
" -t <time> Automatically play and stop after <time> has passed\n");
94 printout (
"Options set via $ASE_DEBUG:\n");
95 printout (
" :no-logfile: Disable logging to ~/.cache/anklang/ instead of stderr\n");
96 printout (
" :coredump: Allow core dumps of Anklang and its GUI process\n");
101parse_option_arg (
const char *option,
char **argv,
unsigned *ith,
const char **argp)
103 const size_t l =
strlen (option);
104 if (strncmp (option, argv[*ith], l) == 0) {
105 *argp = argv[*ith] + l;
106 argv[*ith] =
nullptr;
107 if ((*argp)[0] ==
'=')
109 else if ((*argp)[0] == 0) {
111 *argp = argv[*ith] ? argv[*ith] :
"";
112 argv[*ith] =
nullptr;
120static constexpr int jsipc_logflags = 1 | 2 | 4 | 8 | 16;
121static constexpr int jsbin_logflags = 1 | 256;
123static StringS check_test_names;
125static String ui_js_script;
128parse_args (
int *argcp,
char **argv, MainAppImpl &config)
138 bool headless_default =
false, headless_option =
false;
140 const uint argc = *argcp;
141 for (
uint i = 1; i < argc; i++)
143 const char *
optarg =
nullptr;
145 config.args.push_back (argv[i]);
146 else if (strcmp (argv[i],
"--fatal-warnings") == 0 || strcmp (argv[i],
"--g-fatal-warnings") == 0)
148 else if (strcmp (
"--disable-randomization", argv[i]) == 0)
149 config.allow_randomization =
false;
150 else if (strcmp (
"--norc", argv[i]) == 0)
152 else if (strcmp (
"--rand64", argv[i]) == 0)
155 constexpr int N = 8192;
159 for (
size_t i = 0; i < N; i++)
160 buffer[i] = prng.next();
161 fwrite (buffer,
sizeof (buffer[0]), N, stdout);
165 else if (strcmp (
"--check", argv[i]) == 0)
167 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
169 printerr (
"CHECK_INTEGRITY_TESTS…\n");
170 default_ui_mode =
"none";
172 else if (strcmp (
"--list-tests", argv[i]) == 0)
175 for (
const auto &t : Test::list_tests())
176 ids.push_back (t.ident);
178 for (
const auto &t : ids)
179 printout (
"%s\n", t);
182 else if (strcmp (
"--list-ui-tests", argv[i]) == 0)
186 fatal_error (
"missing UI test list: %s", testfile);
187 const String content = Path::stringread (testfile);
189 for (
const String &line : lines)
191 printout (
"%s\n", line);
194 else if (strcmp (
"--ui-test", argv[i]) == 0 || strncmp (
"--ui-test=", argv[i], 9) == 0)
196 const char *eq =
strchr (argv[i],
'=');
197 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] :
nullptr;
200 for (
const auto &t : tests)
201 ui_test_names.push_back (t);
204 headless_default =
true;
206 else if (strcmp (
"--ui-js", argv[i]) == 0 || strncmp (
"--ui-js=", argv[i], 8) == 0)
208 const char *eq =
strchr (argv[i],
'=');
209 ui_js_script = eq ? eq + 1 : i+1 < argc ? argv[++i] :
"";
210 if (ui_js_script.
empty())
212 headless_default =
true;
214 else if (strcmp (
"--headless", argv[i]) == 0 || strncmp (
"--headless=", argv[i], 10) == 0)
216 const char *eq =
strchr (argv[i],
'=');
218 headless_option =
true;
220 else if (strcmp (
"--test", argv[i]) == 0 || strncmp (
"--test=", argv[i], 7) == 0)
222 const char *eq =
strchr (argv[i],
'=');
223 const char *arg = eq ? eq + 1 : i+1 < argc ? argv[++i] :
nullptr;
224 config.mode = MainApp::CHECK_INTEGRITY_TESTS;
228 for (
const auto &t : tests)
229 check_test_names.push_back (t);
231 default_ui_mode =
"none";
233 else if (argv[i] ==
String (
"--blake3") && i + 1 <
size_t (argc))
238 printerr (
"%s: failed to read: %s\n", argv[i], strerror (errno));
243 else if (strcmp (
"--jsonts", argv[i]) == 0) {
244 if (getenv (
"ASE_JSONTS") ==
nullptr)
245 fatal_error (
"%s: environment must contain ASE_JSONTS for --jsonts", argv[0]);
246 printout (
"%s\n", Jsonipc::g_binding_printer->finish());
248 }
else if (strcmp (
"--jsipc", argv[i]) == 0)
249 config.jsonapi_logflags |= jsipc_logflags;
250 else if (strcmp (
"--jsbin", argv[i]) == 0)
251 config.jsonapi_logflags |= jsbin_logflags;
252 else if (strcmp (
"--list-drivers", argv[i]) == 0)
253 config.list_drivers =
true;
254 else if (strcmp (
"-M", argv[i]) == 0 && i + 1 <
size_t (argc))
257 config.midi_override = argv[i];
259 else if (strcmp (
"-P", argv[i]) == 0 && i + 1 <
size_t (argc))
262 config.pcm_override = argv[i];
264 else if (strcmp (
"--no-devices", argv[i]) == 0)
266 config.no_devices =
true;
268 else if (strcmp (
"-h", argv[i]) == 0 ||
269 strcmp (
"--help", argv[i]) == 0)
274 else if (strcmp (
"--version", argv[i]) == 0)
279 else if (argv[i] ==
String (
"-o") && i + 1 <
size_t (argc))
282 config.outputfile = argv[i];
284 else if (argv[i] ==
String (
"--play-autostart"))
286 config.play_autostart =
true;
287 default_ui_mode =
"none";
289 else if (parse_option_arg (
"--unauth-dev", argv, &i, &optarg))
292 default_ui_mode =
"wait";
294 else if (argv[i] ==
String (
"-t") && i + 1 <
size_t (argc))
296 config.play_autostart =
true;
299 default_ui_mode =
"none";
301 else if (parse_option_arg (
"--ui", argv, &i, &optarg))
305 else if (argv[i] ==
String (
"--") && !sep)
307 else if (argv[i][0] ==
'-' && !sep)
308 fatal_error (
"invalid command line argument: %s", argv[i]);
310 config.args.push_back (argv[i]);
313 if (!headless_option)
314 config.headless = headless_default;
315 if (arg_ui_mode.
empty())
316 arg_ui_mode = default_ui_mode;
320 for (
uint i = 1; i < argc; i++)
334 const char *
const c52 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
341 KeccakCryptoRng csprng;
343 for (
size_t i = 0; i < 23; ++i)
344 auth += c52[csprng.random() % 52];
351 if (check_test_names.
empty())
361 LoopP loop = main_loop;
374 seen_autostop =
true;
380handle_autostop (
const LoopState &state)
384 case LoopState::PREPARE:
return seen_autostop;
385 case LoopState::CHECK:
return seen_autostop;
386 case LoopState::DISPATCH:
387 info (
"Main: stopping playback (auto)");
399 sigset_t signal_mask;
405 Ase::warning (
"Ase: pthread_sigmask for SIGPIPE failed: %s\n", strerror (errno));
414 struct rlimit rl = {};
415 if (getrlimit (RLIMIT_CORE, &rl) == 0 && rl.rlim_cur != 0) {
417 if (setrlimit (RLIMIT_CORE, &rl) != 0)
418 Ase::warning (
"Ase: setrlimit for RLIMIT_CORE failed: %s\n", strerror (errno));
428 if (!loft_needs_preallocation_mt)
430 loft_needs_preallocation_mt =
true;
435static size_t last_loft_preallocation = 0;
438preallocate_loft (
size_t preallocation)
441 last_loft_preallocation = preallocation;
444 .watermark = last_loft_preallocation / 2,
445 .flags = Loft::PREFAULT_PAGES,
447 loft_set_config (loftcfg);
448 loft_set_notifier (notify_loft_lowmem);
449 loft_grow_preallocate();
456 const bool keep_alive = lstate.phase == LoopState::DISPATCH;
460 loft_needs_preallocation_mt =
false;
461 last_loft_preallocation *= 2;
462 const size_t newalloc = loft_grow_preallocate (last_loft_preallocation);
464 loft_get_config (config);
465 config.
watermark = last_loft_preallocation / 2;
466 loft_set_config (config);
468 MDEBUG (
"Loft preallocation in main thread: %f MB", newalloc / (1024. * 1024));
473prefault_pages (
size_t stacksize,
size_t heapsize)
475 const size_t pagesize =
sysconf (_SC_PAGESIZE);
476 char *heap = (
char*) malloc (heapsize);
478 for (
size_t i = 0; i < heapsize; i += pagesize)
481 char *stack = (
char*) alloca (stacksize);
483 for (
size_t i = 0; i < stacksize; i += pagesize)
488main (
int argc,
char *argv[])
491 using namespace AnsiColors;
494 TaskRegistry::setup_ase (
"AnklangMainProc");
496 mallopt (M_MMAP_MAX, 0);
498 mallopt (M_TRIM_THRESHOLD, -1);
500 prefault_pages ((1024 + 768) * 1024, 64 * 1024 * 1024);
502 preallocate_loft (64 * 1024 * 1024);
504 loft_set_growth_notifier ([] (
size_t total,
size_t needed)
506 warning (
"Loft.BumpAllocator: growing beyond preallocation: totalmem=%u needed=%d\n", total, needed);
510 logging_handle_terminate();
515 atquit_make_subreaper();
518 disable_core_dumps();
521 if (!setlocale (LC_ALL,
""))
522 fatal_error (
"setlocale: locale not supported by libc: %s", ::strerror (errno));
525 parse_args (&argc, argv, main_app);
526 main_app.ui_tests = ui_test_names;
527 main_app.ui_js = ui_js_script;
528 const int socket_port = arg_unauth_port > 0 ? arg_unauth_port : 0;
529 const char *socket_host =
"127.0.0.1";
530 const auto socket_info = WebSocketServer::bind_port (socket_host, socket_port);
531 logging_configure (arg_ui_mode !=
"none" ?
string_format (
"%u", socket_info.port) :
"");
538 Preference::load_preferences (
true);
541 ServerImpl::instancep();
544 if (!
trkn_init (argc, argv, App.no_devices))
545 fatal_error (
"Main: failed to initialize tracktion engine");
547 const auto B1 =
color (BOLD);
548 const auto B0 =
color (BOLD_OFF);
552 if (App.list_drivers)
554 Ase::Driver::EntryVec entries;
555 printout (
"%s",
_(
"Available PCM drivers:\n"));
556 entries = Ase::PcmDriver::list_drivers();
557 std::sort (entries.begin(), entries.end(), [] (
auto &a,
auto &b) { return a.priority < b.priority; });
558 for (
const auto &entry : entries)
560 printout (
" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid +
":",
561 entry.readonly ?
"Input" : entry.writeonly ?
"Output" :
"Duplex",
562 entry.priority, entry.device_name,
563 entry.capabilities.
empty() ?
"" :
"\t" + entry.capabilities +
"\n",
564 entry.device_info.
empty() ?
"" :
"\t" + entry.device_info +
"\n",
565 entry.hints.
empty() ?
"" :
"\t(" + entry.hints +
")\n",
566 entry.notice.
empty() ?
"" :
"\t" + entry.notice +
"\n");
570 printout (
"%s",
_(
"Available MIDI drivers:\n"));
571 entries = Ase::MidiDriver::list_drivers();
572 std::sort (entries.begin(), entries.end(), [] (
auto &a,
auto &b) { return a.priority < b.priority; });
573 for (
const auto &entry : entries)
575 printout (
" %-30s (%s, %08x)\n\t%s\n%s%s%s%s", entry.devid +
":",
576 entry.readonly ?
"Input" : entry.writeonly ?
"Output" :
"Duplex",
577 entry.priority, entry.device_name,
578 entry.capabilities.
empty() ?
"" :
"\t" + entry.capabilities +
"\n",
579 entry.device_info.
empty() ?
"" :
"\t" + entry.device_info +
"\n",
580 entry.hints.
empty() ?
"" :
"\t(" + entry.hints +
")\n",
581 entry.notice.
empty() ?
"" :
"\t" + entry.notice +
"\n");
590 for (
const auto &filename : App.args)
592 preload_project = ProjectImpl::create (
Path::basename (filename));
593 Error error = Error::NO_MEMORY;
595 error = preload_project->load_project (filename);
596 diag (
"Main: load project: %s: %s", filename,
ase_error_blurb (error));
598 warning (
"%s: failed to load project: %s", filename,
ase_error_blurb (error));
602 const String auth_token = arg_unauth_port > 0 ?
"" : make_auth_string();
603 auto wss = WebSocketServer::create (jsonapi_make_connection, App.jsonapi_logflags, auth_token);
604 main_app.web_socket_server = &*wss;
607 wss->http_alias (
"/Builtin/Controller",
anklang_runpath (RPath::INSTALLDIR,
"/Controller"));
609 wss->http_alias (
"/Builtin/Scripts",
anklang_runpath (RPath::INSTALLDIR,
"/Scripts"));
610 const String subprotocol =
"";
611 jsonapi_set_subprotocol (subprotocol);
612 if (App.mode == MainApp::SYNTHENGINE && arg_ui_mode !=
"none") {
613 wss->listen (socket_info, [] () { main_loop->quit (-1); });
616 String redirecthtml = webui_create_auth_redirect (
"anklang", wss->listen_port(), auth_token, arg_ui_mode);
618 fatal_error (
"%s: failed to create html redirect file in $HOME", redirecthtml);
619 webui_url =
"file://" + redirecthtml;
620 wss->see_other (webui_url);
622 info (
"Main: WebUI address: %s", webui_url);
624 WebuiFlags webui_flags = main_app.headless ? WebuiFlags::HEADLESS : WebuiFlags::NONE;
625 if (main_app.ui_tests.
size())
626 webui_flags = webui_flags | WebuiFlags::CONSOLE_LOGS;
627 auto ereason = webui_start_browser (arg_ui_mode, main_loop, webui_url,
630 main_loop->quit (exit_code);
634 fatal_error (
"Main: failed to run WebUI: %s: %s", ereason.what, ::strerror (ereason.error));
638 for (
int sigid : { SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGTERM, SIGSYS }) {
639 main_loop->exec_usignal (sigid, [] (
int8 sig) {
640 info (
"Main: got signal %d: terminate", sig);
641 atquit_terminate (-1);
644 USignalSource::install_sigaction (sigid);
648 main_loop->exec_usignal (SIGUSR2, [wss] (
int8 sig) {
649 info (
"Main: got signal %d: reset WebSocket", sig);
653 USignalSource::install_sigaction (SIGUSR2);
660 if (App.play_autostart && preload_project)
661 main_loop->add ([preload_project] ()
663 info (
"Main: starting playback (auto)");
664 preload_project->start_playback (App.play_autostop);
667 main_loop->exec_dispatcher (handle_autostop);
670 main_loop->add ([] ()
676 if (App.mode == MainApp::CHECK_INTEGRITY_TESTS)
677 main_loop->add (run_tests_and_quit);
680 const int exitcode = main_loop->run();
682 diag (
"Main: event loop quit: code=%d", exitcode);
686 main_app.web_socket_server =
nullptr;
690 ProjectImpl::force_shutdown_all();
693 main_loop->iterate_pending();
698 diag (
"Main: exiting: %d", exitcode);
699 atquit_terminate (exitcode);
706main (
int argc,
char *argv[])
709#ifdef ASE_WITH_CPPTRACE
710 CPPTRACE_TRY { r = Ase::main (argc, argv); }
718 cpptrace::from_current_exception().print();
721 r = Ase::main (argc, argv);
static String priority_string(uint priority)
Return string which represents the given priority mask.
static LoopP current()
Return the thread-local singleton loop, created on first call.
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
#define _(...)
Retrieve the translation of a C or C++ string.
std::string color(Colors acolor, Colors c1, Colors c2, Colors c3, Colors c4, Colors c5, Colors c6)
Return ANSI code for the specified color if stdout & stderr should be colorized, see colorize_tty().
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
bool check(const String &file, const String &mode)
int run(void)
Run all registered tests.
The Anklang C++ API namespace.
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
bool trkn_init(int argc, char *argv[], bool nodevs)
Setup tracktion and tracktion::engine.
StringS string_split(const String &string, const String &splitter, size_t maxn)
Split a string, using splitter as delimiter.
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
void logging_prune_old_logs(double age_seconds)
Delete log files older than age seconds.
int8_t int8
An 8-bit signed integer.
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
JobQueue main_jobs(call_main_loop)
Execute a job callback in the event loop.
Error
Enum representing Error states.
const char * ase_error_blurb(Error error)
Describe Error condition.
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
double string_to_seconds(const String &string, double fallback)
Parse string into seconds.
int64 string_to_int(const String &string, size_t *consumed, uint base)
Parse a string into a 64bit integer, optionally specifying the expected number base.
void main_loop_wakeup()
Wake up the event loop.
bool logging_fatal_warnings
Global flag to cause the program to abort on warnings.
@ IDLE
Mildly important, used for background tasks.
@ SYSALLOC
Internal maintenance, don't use.
const char * ase_version()
Provide a string containing the package version.
std::string String
Convenience alias for std::string.
std::string executable_name()
Retrieve the name part of executable_path().
bool string_to_bool(const String &string, bool fallback)
Interpret a string as boolean value.
void main_loop_autostop_mt()
Stop the event loop after a timeout.
bool debug_key_enabled(const char *conditional) noexcept
Check if conditional is enabled by $ASE_DEBUG.
uint32_t uint
Provide 'uint' as convenience type.
void load_registered_drivers()
Load all registered drivers.
size_t preallocate
Amount of preallocated available memory.
size_t watermark
Watermark to trigger async preallocation.
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
const char * ase_build_id()
Provide a string containing the ASE library build id.
Configuration for Loft allocations.
size_t hash(size_t seed, const T &v)