#include "app.h" #include "arena.h" #include "string.h" #include "scratch.h" #include "sys.h" #include "work.h" #include "user.h" #include "sim.h" #include "sim.h" #include "playback.h" #include "log.h" #include "resource.h" #include "asset_cache.h" #include "font.h" #include "sprite.h" #include "ttf.h" #include "mixer.h" #include "sound.h" #include "util.h" #include "settings.h" #include "draw.h" #include "math.h" #include "gpu.h" #include "phys.h" #include "sock.h" #include "host.h" #include "bitbuff.h" struct exit_callback { app_exit_callback_func *func; struct exit_callback *next; }; GLOBAL struct { struct arena *arena; struct string write_path; struct sync_flag exit_sf; /* Exit callbacks */ struct sys_mutex exit_callbacks_mutex; struct arena *exit_callbacks_arena; struct exit_callback *exit_callbacks_head; } G = ZI, DEBUG_ALIAS(G, G_app); /* ========================== * * Write directory * ========================== */ INTERNAL struct string initialize_write_directory(struct arena *arena, struct string write_dir) { struct arena_temp scratch = scratch_begin(arena); /* Create write path */ struct string base_write_dir = sys_get_write_path(scratch.arena); struct string write_path_fmt = base_write_dir.len > 0 ? LIT("%F/%F/") : LIT("%F%F/"); struct string write_path = string_format( arena, write_path_fmt, FMT_STR(base_write_dir), FMT_STR(write_dir) ); /* Create write dir if not present */ if (!sys_is_dir(write_path)) { sys_mkdir(write_path); /* TODO: handle failure */ } scratch_end(scratch); return write_path; } struct string app_write_path_cat(struct arena *arena, struct string filename) { return string_cat(arena, G.write_path, filename); } /* ========================== * * Default settings * ========================== */ INTERNAL struct sys_window_settings default_window_settings(struct sys_window *window) { __prof; struct v2 monitor_size = sys_window_get_monitor_size(window); i32 width = 1280; i32 height = math_round_to_int(width / (f32)(DEFAULT_CAMERA_WIDTH / DEFAULT_CAMERA_HEIGHT)); i32 x = math_round_to_int(monitor_size.x / 2.f - width / 2); i32 y = math_round_to_int(monitor_size.y / 2.f - height / 2); return (struct sys_window_settings) { .title = WINDOW_TITLE, .floating_x = x, .floating_y = y, .floating_width = width, .floating_height = height }; } /* ========================== * * Exit callbacks * ========================== */ void app_register_exit_callback(app_exit_callback_func *func) { struct sys_lock lock = sys_mutex_lock_e(&G.exit_callbacks_mutex); struct exit_callback *callback = arena_push(G.exit_callbacks_arena, struct exit_callback); callback->func = func; callback->next = G.exit_callbacks_head; G.exit_callbacks_head = callback; sys_mutex_unlock(&lock); } /* ========================== * * Args * ========================== */ struct app_arg { struct string key; struct string value; struct app_arg *next; }; struct app_arg_list { struct app_arg *first; struct app_arg *last; u64 count; }; /* TODO: Remove this and do real argument parsing */ INTERNAL struct app_arg_list parse_args(struct arena *arena, struct string args_str) { struct app_arg_list res = ZI; i64 mode = 0; i64 i = 0; i64 key_start = -1; i64 key_end = -1; i64 value_start = -1; i64 value_end = -1; while (i < (i64)args_str.len) { u8 c = args_str.text[i]; switch (mode) { case 0: { if (c == '-') { mode = 1; key_start = i + 1; } ++i; } break; case 1: { if (c == '=') { key_end = i; value_start = i + 1; mode = 2; } ++i; } break; case 2: { if (c == '-' || i == (i64)args_str.len - 1) { if (c == '-') { value_end = i; } else { value_end = i + 1; } if (key_start >= 0 && key_end > key_start && key_end <= (i64)args_str.len && value_start >= 0 && value_end > value_start && value_end <= (i64)args_str.len) { struct string key = string_copy(arena, STRING(key_end - key_start, args_str.text + key_start)); struct string value = string_copy(arena, STRING(value_end - value_start, args_str.text + value_start)); struct app_arg *arg = arena_push(arena, struct app_arg); arg->key = key; arg->value = value; if (res.last) { res.last->next = arg; } else { res.first = arg; } res.last = arg; ++res.count; } key_start = i + 1; mode = 1; } ++i; } break; default: break; } } return res; } /* ========================== * * Entry point * ========================== */ void app_entry_point(struct string args_str) { struct arena_temp scratch = scratch_begin_no_conflict(); struct app_arg_list args = parse_args(scratch.arena, args_str); struct string logfile_name = LIT("log.log"); struct string settings_file_name = LIT("settings.txt"); struct string connect_address = ZI; for (struct app_arg *arg = args.first; arg; arg = arg->next) { struct string key = arg->key; struct string value = arg->value; if (string_eq(key, LIT("log"))) { logfile_name = value; } else if (string_eq(key, LIT("settings"))) { settings_file_name = value; } else if (string_eq(key, LIT("connect"))) { connect_address = value; } } #if !RTC /* Verify test modes aren't left on by accident in release mode */ CT_ASSERT(BITBUFF_DEBUG == 0); CT_ASSERT(BITBUFF_TEST == 0); #endif #if BITBUFF_TEST bitbuff_test(); #endif G.exit_sf = sync_flag_alloc(); G.exit_callbacks_mutex = sys_mutex_alloc(); G.exit_callbacks_arena = arena_alloc(GIGABYTE(64)); G.arena = arena_alloc(GIGABYTE(64)); u32 worker_count = 4; { /* FIXME: Switch this on to utilize all cores. Only decreasing worker count for testing purposes. */ #if !PROFILING && !RTC /* Ideally these layers should have cores "reserved" for them * 1. User thread * 2. Sim thread * 3. Audio mixing / playback thread * 4. Networking thread */ i32 num_reserved_cores = 4; i32 num_logical_cores = (i32)sys_num_logical_processors(); //num_logical_cores = min(num_logical_cores, 8) + (max(num_logical_cores - 8, 0) / 2); /* Dumb heuristic to try and lessen e-core usage */ i32 min_worker_count = 2; i32 max_worker_count = 128; i32 target_worker_count = num_logical_cores - num_reserved_cores; worker_count = (u32)clamp_i32(target_worker_count, min_worker_count, max_worker_count); #endif } G.write_path = initialize_write_directory(G.arena, LIT(WRITE_DIR)); /* Startup logging */ { struct arena_temp temp = arena_temp_begin(scratch.arena); struct string logfile_dir = string_cat(temp.arena, G.write_path, LIT("logs/")); struct string logfile_path = string_cat(temp.arena, logfile_dir, logfile_name); sys_mkdir(logfile_dir); log_startup(logfile_path); logf_info("Start of logs"); arena_temp_end(temp); } logf_info("App started with args \"%F\" (%F parsed)", FMT_STR(args_str), FMT_UINT(args.count)); for (struct app_arg *arg = args.first; arg; arg = arg->next) { logf_info("Parsed arg: key = \"%F\", value = \"%F\"", FMT_STR(arg->key), FMT_STR(arg->value)); } /* Create window */ struct sys_window window = sys_window_alloc(); /* Read window settings from file */ { struct arena_temp temp = arena_temp_begin(scratch.arena); struct sys_window_settings window_settings = ZI; struct string settings_path = app_write_path_cat(temp.arena, settings_file_name); logf_info("Looking for settings file \"%F\"", FMT_STR(settings_path)); if (sys_is_file(settings_path)) { logf_info("Settings file found"); struct sys_file settings_file = sys_file_open_read(settings_path); struct string file_data = sys_file_read_all(temp.arena, settings_file); sys_file_close(settings_file); logf_info("Deserializing settings file data: %F", FMT_STR(file_data)); struct string error = ZI; struct sys_window_settings *res = settings_deserialize(temp.arena, file_data, &error); if (error.len > 0) { logf_info("Failed to load settings file with error - %F", FMT_STR(error)); struct string msg = string_format(temp.arena, LIT( "Failed to loading settings file \"%F\":\n" "------------\n" "%F\n" "------------\n" "To stop this error from appearing, either fix the issue above or delete the file from the system." ), FMT_STR(settings_path), FMT_STR(error)); sys_panic(msg); } logf_info("Settings file loaded successfully"); window_settings = *res; } else { logf_info("Settings file not found, loading default"); window_settings = default_window_settings(&window); } string_copy_to_string(STRING_FROM_ARRAY(window_settings.title), LIT(WINDOW_TITLE)); sys_window_update_settings(&window, &window_settings); arena_temp_end(temp); } /* Startup systems */ struct sock_startup_receipt sock_sr = sock_startup(); struct host_startup_receipt host_sr = host_startup(&sock_sr); struct resource_startup_receipt resource_sr = resource_startup(); struct work_startup_receipt work_sr = work_startup(worker_count); struct gpu_startup_receipt gpu_sr = gpu_startup(&work_sr, &window); struct asset_cache_startup_receipt asset_cache_sr = asset_cache_startup(&work_sr); struct ttf_startup_receipt ttf_sr = ttf_startup(); struct font_startup_receipt font_sr = font_startup(&work_sr, &gpu_sr, &asset_cache_sr, &ttf_sr, &resource_sr); struct sprite_startup_receipt sprite_sr = sprite_startup(&gpu_sr, &resource_sr); struct mixer_startup_receipt mixer_sr = mixer_startup(); struct sound_startup_receipt sound_sr = sound_startup(&work_sr, &asset_cache_sr, &resource_sr); struct draw_startup_receipt draw_sr = draw_startup(&gpu_sr, &font_sr); struct sim_startup_receipt sim_sr = sim_startup(); struct user_startup_receipt user_sr = user_startup(&work_sr, &gpu_sr, &font_sr, &sprite_sr, &draw_sr, &asset_cache_sr, &sound_sr, &mixer_sr, &host_sr, &sim_sr, connect_address, &window); struct playback_startup_receipt playback_sr = playback_startup(&mixer_sr); (UNUSED)user_sr; (UNUSED)playback_sr; /* Show window */ sys_window_show(&window); /* Wait for app_exit() */ sync_flag_wait(&G.exit_sf); /* Run exit callbacks */ /* FIXME: Only wait on shutdown for a certain period of time before * forcing process exit (to prevent process hanging in the background * if something gets stuck) */ { __profscope(Run exit callbacks); struct sys_lock lock = sys_mutex_lock_e(&G.exit_callbacks_mutex); for (struct exit_callback *callback = G.exit_callbacks_head; callback; callback = callback->next) { callback->func(); } sys_mutex_unlock(&lock); } /* Write window settings to file */ { __profscope(Write settings file); struct arena_temp temp = arena_temp_begin(scratch.arena); struct string window_settings_path = app_write_path_cat(temp.arena, settings_file_name); struct sys_window_settings settings = sys_window_get_settings(&window); struct string str = settings_serialize(temp.arena, &settings); logf_info("Serialized window settings: %F", FMT_STR(str)); logf_info("Writing settings file to path \"%F\"", FMT_STR(window_settings_path)); struct sys_file settings_file = sys_file_open_write(window_settings_path); sys_file_write(settings_file, str); sys_file_close(settings_file); logf_info("Finished writing settings file"); arena_temp_end(temp); } sys_window_release(&window); logf_info("Program exited normally"); scratch_end(scratch); } void app_exit(void) { sync_flag_set(&G.exit_sf); }