mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-02-05 13:16:11 +00:00
improved frame pacing, where frame delaying is done just before displaying the frame. so, the frame is fully rendered, and then the delay is done to wait the extra time before displaying the frame. This is a far more reliable and consistent way to manage frame pacing, and it will help to keep the framerate from choking too badly when the game lags a little. The changes to `gfx_end_frame` is to minimize the steps necessary between delaying and actually displaying the frame. This is an optimization just to increase the accuracy of frame pacing even further. Also, delta calculation is improved when the number of frames to draw is known. Djui cursor movement is also updated every frame for smoother movement.
627 lines
18 KiB
C
627 lines
18 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sm64.h"
|
|
|
|
#include "pc/lua/smlua.h"
|
|
#include "pc/lua/utils/smlua_text_utils.h"
|
|
#include "game/memory.h"
|
|
#include "audio/data.h"
|
|
#include "audio/external.h"
|
|
|
|
#include "network/network.h"
|
|
#include "lua/smlua.h"
|
|
|
|
#include "audio/audio_api.h"
|
|
#include "audio/audio_sdl.h"
|
|
#include "audio/audio_null.h"
|
|
|
|
#include "rom_assets.h"
|
|
#include "rom_checker.h"
|
|
#include "pc_main.h"
|
|
#include "loading.h"
|
|
#include "cliopts.h"
|
|
#include "configfile.h"
|
|
#include "thread.h"
|
|
#include "controller/controller_api.h"
|
|
#include "controller/controller_keyboard.h"
|
|
#include "controller/controller_mouse.h"
|
|
#include "fs/fs.h"
|
|
|
|
#include "game/display.h" // for gGlobalTimer
|
|
#include "game/game_init.h"
|
|
#include "game/main.h"
|
|
#include "game/rumble_init.h"
|
|
|
|
#include "pc/lua/utils/smlua_audio_utils.h"
|
|
|
|
#include "pc/network/version.h"
|
|
#include "pc/network/socket/socket.h"
|
|
#include "pc/network/network_player.h"
|
|
#include "pc/update_checker.h"
|
|
#include "pc/djui/djui.h"
|
|
#include "pc/djui/djui_unicode.h"
|
|
#include "pc/djui/djui_panel.h"
|
|
#include "pc/djui/djui_panel_modlist.h"
|
|
#include "pc/djui/djui_ctx_display.h"
|
|
#include "pc/djui/djui_fps_display.h"
|
|
#include "pc/djui/djui_lua_profiler.h"
|
|
#include "pc/debuglog.h"
|
|
#include "pc/utils/misc.h"
|
|
#include "pc/mods/mods.h"
|
|
|
|
#include "debug_context.h"
|
|
#include "menu/intro_geo.h"
|
|
|
|
#include "gfx_dimensions.h"
|
|
#include "game/segment2.h"
|
|
|
|
#include "engine/math_util.h"
|
|
|
|
#ifdef DISCORD_SDK
|
|
#include "pc/discord/discord.h"
|
|
#endif
|
|
|
|
#include "pc/mumble/mumble.h"
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SDL2
|
|
#include <SDL2/SDL.h>
|
|
#endif
|
|
|
|
extern Vp gViewportFullscreen;
|
|
|
|
OSMesg D_80339BEC;
|
|
OSMesgQueue gSIEventMesgQueue;
|
|
|
|
s8 gResetTimer;
|
|
s8 D_8032C648;
|
|
s8 gDebugLevelSelect;
|
|
s8 gShowProfiler;
|
|
s8 gShowDebugText;
|
|
|
|
s32 gRumblePakPfs;
|
|
u32 gNumVblanks = 0;
|
|
|
|
u8 gRenderingInterpolated = 0;
|
|
f32 gRenderingDelta = 0;
|
|
f32 gFramePercentage = 0.f;
|
|
|
|
#define FRAMERATE 30
|
|
static const f64 sFrameTime = (1.0 / ((double)FRAMERATE));
|
|
static f64 sFpsTimeLast = 0;
|
|
static f64 sFrameTimeStart = 0;
|
|
static u32 sDrawnFrames = 0;
|
|
|
|
bool gGameInited = false;
|
|
bool gGfxInited = false;
|
|
|
|
f32 gMasterVolume;
|
|
|
|
u8 gLuaVolumeMaster = 127;
|
|
u8 gLuaVolumeLevel = 127;
|
|
u8 gLuaVolumeSfx = 127;
|
|
u8 gLuaVolumeEnv = 127;
|
|
|
|
static struct AudioAPI *audio_api;
|
|
struct GfxWindowManagerAPI *wm_api = &WAPI;
|
|
|
|
extern void gfx_run(Gfx *commands);
|
|
extern void thread5_game_loop(void *arg);
|
|
extern void create_next_audio_buffer(s16 *samples, u32 num_samples);
|
|
void game_loop_one_iteration(void);
|
|
|
|
void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {}
|
|
void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {}
|
|
|
|
void send_display_list(struct SPTask *spTask) {
|
|
if (!gGameInited) { return; }
|
|
gfx_run((Gfx *)spTask->task.t.data_ptr);
|
|
}
|
|
|
|
#ifdef VERSION_EU
|
|
#define SAMPLES_HIGH 560 // gAudioBufferParameters.maxAiBufferLength
|
|
#define SAMPLES_LOW 528 // gAudioBufferParameters.minAiBufferLength
|
|
#else
|
|
#define SAMPLES_HIGH 544
|
|
#define SAMPLES_LOW 528
|
|
#endif
|
|
|
|
extern void patch_mtx_before(void);
|
|
extern void patch_screen_transition_before(void);
|
|
extern void patch_title_screen_before(void);
|
|
extern void patch_dialog_before(void);
|
|
extern void patch_hud_before(void);
|
|
extern void patch_paintings_before(void);
|
|
extern void patch_bubble_particles_before(void);
|
|
extern void patch_snow_particles_before(void);
|
|
extern void patch_djui_before(void);
|
|
extern void patch_djui_hud_before(void);
|
|
extern void patch_scroll_targets_before(void);
|
|
|
|
extern void patch_mtx_interpolated(f32 delta);
|
|
extern void patch_screen_transition_interpolated(f32 delta);
|
|
extern void patch_title_screen_interpolated(f32 delta);
|
|
extern void patch_dialog_interpolated(f32 delta);
|
|
extern void patch_hud_interpolated(f32 delta);
|
|
extern void patch_paintings_interpolated(f32 delta);
|
|
extern void patch_bubble_particles_interpolated(f32 delta);
|
|
extern void patch_snow_particles_interpolated(f32 delta);
|
|
extern void patch_djui_interpolated(f32 delta);
|
|
extern void patch_djui_hud(f32 delta);
|
|
extern void patch_scroll_targets_interpolated(f32 delta);
|
|
|
|
static void patch_interpolations_before(void) {
|
|
patch_mtx_before();
|
|
patch_screen_transition_before();
|
|
patch_title_screen_before();
|
|
patch_dialog_before();
|
|
patch_hud_before();
|
|
patch_paintings_before();
|
|
patch_bubble_particles_before();
|
|
patch_snow_particles_before();
|
|
patch_djui_before();
|
|
patch_djui_hud_before();
|
|
patch_scroll_targets_before();
|
|
}
|
|
|
|
static inline void patch_interpolations(f32 delta) {
|
|
patch_mtx_interpolated(delta);
|
|
patch_screen_transition_interpolated(delta);
|
|
patch_title_screen_interpolated(delta);
|
|
patch_dialog_interpolated(delta);
|
|
patch_hud_interpolated(delta);
|
|
patch_paintings_interpolated(delta);
|
|
patch_bubble_particles_interpolated(delta);
|
|
patch_snow_particles_interpolated(delta);
|
|
patch_djui_interpolated(delta);
|
|
patch_djui_hud(delta);
|
|
patch_scroll_targets_interpolated(delta);
|
|
}
|
|
|
|
static void compute_fps(f64 curTime) {
|
|
u32 fps = round((f64) sDrawnFrames / MAX(0.001, curTime - sFpsTimeLast));
|
|
djui_fps_display_update(fps);
|
|
sFpsTimeLast = curTime;
|
|
sDrawnFrames = 0;
|
|
}
|
|
|
|
static s32 get_num_frames_to_draw(f64 t, u32 frameLimit) {
|
|
if (frameLimit % FRAMERATE == 0) {
|
|
return frameLimit / FRAMERATE;
|
|
}
|
|
s64 numFramesCurr = (s64) (t * (f64) frameLimit);
|
|
s64 numFramesNext = (s64) ((t + sFrameTime) * (f64) frameLimit);
|
|
return (s32) MAX(1, numFramesNext - numFramesCurr);
|
|
}
|
|
|
|
static u32 get_display_refresh_rate() {
|
|
#ifdef HAVE_SDL2
|
|
static u32 refreshRate = 0;
|
|
if (!refreshRate) {
|
|
SDL_DisplayMode mode;
|
|
if (SDL_GetCurrentDisplayMode(0, &mode) == 0) {
|
|
if (mode.refresh_rate > 0) { refreshRate = (u32) mode.refresh_rate; }
|
|
} else {
|
|
refreshRate = 60;
|
|
}
|
|
}
|
|
return refreshRate;
|
|
#else
|
|
return 60;
|
|
#endif
|
|
}
|
|
|
|
static u32 get_target_refresh_rate() {
|
|
if (configFramerateMode == RRM_MANUAL) { return configFrameLimit; }
|
|
if (configFramerateMode == RRM_UNLIMITED) { return 3000; } // Has no effect
|
|
return get_display_refresh_rate();
|
|
}
|
|
|
|
void produce_interpolation_frames_and_delay(void) {
|
|
u32 refreshRate = get_target_refresh_rate();
|
|
|
|
gRenderingInterpolated = true;
|
|
|
|
u32 displayRefreshRate = get_display_refresh_rate();
|
|
bool shouldDelay = configFramerateMode != RRM_UNLIMITED;
|
|
if (configWindow.vsync && displayRefreshRate <= refreshRate) {
|
|
shouldDelay = false;
|
|
refreshRate = displayRefreshRate;
|
|
}
|
|
|
|
f64 targetTime = sFrameTimeStart + sFrameTime;
|
|
s32 numFramesToDraw = get_num_frames_to_draw(sFrameTimeStart, refreshRate);
|
|
|
|
f64 curTime = clock_elapsed_f64();
|
|
f64 loopStartTime = curTime;
|
|
f64 expectedTime = 0;
|
|
u16 framesDrawn = 0;
|
|
const f64 interpFrameTime = sFrameTime / (f64) numFramesToDraw;
|
|
|
|
// interpolate and render
|
|
// make sure to draw at least one frame to prevent the game from freezing completely
|
|
// (including inputs and window events) if the game update duration is greater than 33ms
|
|
do {
|
|
++framesDrawn;
|
|
|
|
// when we know how many frames to draw, use a precise delta
|
|
f64 idealTime = shouldDelay ? (sFrameTimeStart + interpFrameTime * framesDrawn) : curTime;
|
|
f32 delta = clamp((idealTime - sFrameTimeStart) / sFrameTime, 0.f, 1.f);
|
|
gFramePercentage = clamp((curTime - sFrameTimeStart) / sFrameTime, 0.f, 1.f);
|
|
gRenderingDelta = delta;
|
|
|
|
gfx_start_frame();
|
|
if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); }
|
|
send_display_list(gGfxSPTask);
|
|
gfx_end_frame_render();
|
|
|
|
// delay if our framerate is capped
|
|
if (shouldDelay) {
|
|
expectedTime += (targetTime - curTime) / (f64) numFramesToDraw;
|
|
f64 now = clock_elapsed_f64();
|
|
f64 elapsedTime = now - loopStartTime;
|
|
f64 delay = (expectedTime - elapsedTime);
|
|
if (delay > 0.0) {
|
|
precise_delay_f64(delay);
|
|
}
|
|
}
|
|
|
|
// send the frame to the screen (should be directly after the delay for good frame pacing)
|
|
gfx_display_frame();
|
|
sDrawnFrames++;
|
|
if (shouldDelay) { numFramesToDraw--; }
|
|
} while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0);
|
|
|
|
// compute and update the frame rate every second
|
|
if ((curTime = clock_elapsed_f64()) >= sFpsTimeLast + 1.0) {
|
|
compute_fps(curTime);
|
|
}
|
|
|
|
// advance frame start time
|
|
if (curTime > sFrameTimeStart + 2 * sFrameTime) {
|
|
sFrameTimeStart = curTime;
|
|
} else {
|
|
sFrameTimeStart += sFrameTime;
|
|
}
|
|
|
|
gRenderingInterpolated = false;
|
|
}
|
|
|
|
// It's just better to have this off the stack, Because the size isn't small.
|
|
// It also may help static analysis and bug catching.
|
|
static s16 sAudioBuffer[SAMPLES_HIGH * 2 * 2] = { 0 };
|
|
|
|
inline static void buffer_audio(void) {
|
|
bool shouldMute = (configMuteFocusLoss && !WAPI.has_focus()) || (gMasterVolume == 0);
|
|
if (!shouldMute) {
|
|
set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f);
|
|
set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * (f32)gLuaVolumeSfx / 127.0f);
|
|
set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * (f32)gLuaVolumeEnv / 127.0f);
|
|
}
|
|
|
|
int samplesLeft = audio_api->buffered();
|
|
u32 numAudioSamples = samplesLeft < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW;
|
|
for (s32 i = 0; i < 2; i++) {
|
|
create_next_audio_buffer(sAudioBuffer + i * (numAudioSamples * 2), numAudioSamples);
|
|
}
|
|
|
|
if (!shouldMute) {
|
|
for (u16 i=0; i < ARRAY_COUNT(sAudioBuffer); i++) {
|
|
sAudioBuffer[i] *= gMasterVolume;
|
|
}
|
|
audio_api->play((u8 *)sAudioBuffer, 2 * numAudioSamples * 4);
|
|
}
|
|
}
|
|
|
|
void *audio_thread(UNUSED void *arg) {
|
|
// As long as we have an audio api and that we're threaded, Loop.
|
|
while (audio_api) {
|
|
f64 curTime = clock_elapsed_f64();
|
|
|
|
// Buffer the audio.
|
|
lock_mutex(&gAudioThread);
|
|
buffer_audio();
|
|
unlock_mutex(&gAudioThread);
|
|
|
|
// Delay till the next frame for smooth audio at the correct speed.
|
|
// delay
|
|
f64 targetDelta = 1.0 / (f64)FRAMERATE;
|
|
f64 now = clock_elapsed_f64();
|
|
f64 actualDelta = now - curTime;
|
|
if (actualDelta < targetDelta) {
|
|
f64 delay = ((targetDelta - actualDelta) * 1000.0);
|
|
WAPI.delay((u32)delay);
|
|
}
|
|
}
|
|
|
|
// Exit the thread if our loop breaks.
|
|
exit_thread();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void produce_one_frame(void) {
|
|
CTX_EXTENT(CTX_NETWORK, network_update);
|
|
|
|
CTX_EXTENT(CTX_INTERP, patch_interpolations_before);
|
|
|
|
CTX_EXTENT(CTX_GAME_LOOP, game_loop_one_iteration);
|
|
|
|
CTX_EXTENT(CTX_SMLUA, smlua_update);
|
|
|
|
// If we aren't threaded
|
|
if (gAudioThread.state == INVALID) {
|
|
CTX_EXTENT(CTX_AUDIO, buffer_audio);
|
|
}
|
|
|
|
CTX_EXTENT(CTX_RENDER, produce_interpolation_frames_and_delay);
|
|
}
|
|
|
|
// used for rendering 2D scenes fullscreen like the loading or crash screens
|
|
void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB) {
|
|
// measure frame start time
|
|
f64 frameStart = clock_elapsed_f64();
|
|
f64 targetFrameTime = 1.0 / 60.0; // update at 60fps
|
|
|
|
// start frame
|
|
gfx_start_frame();
|
|
config_gfx_pool();
|
|
init_render_image();
|
|
create_dl_ortho_matrix();
|
|
djui_gfx_displaylist_begin();
|
|
|
|
// fix scaling issues
|
|
gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&gViewportFullscreen));
|
|
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - BORDER_HEIGHT);
|
|
|
|
// clear screen
|
|
create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.f, 0.f);
|
|
create_dl_scale_matrix(MENU_MTX_NOPUSH, (GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT) / 130.f, 3.f, 1.f);
|
|
gDPSetEnvColor(gDisplayListHead++, clearColorR, clearColorG, clearColorB, 0xFF);
|
|
gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box);
|
|
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
|
|
|
|
// call the callback
|
|
callback();
|
|
|
|
// render frame
|
|
djui_gfx_displaylist_end();
|
|
end_master_display_list();
|
|
alloc_display_list(0);
|
|
gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list
|
|
display_and_vsync();
|
|
|
|
// delay to go easy on the cpu
|
|
f64 frameEnd = clock_elapsed_f64();
|
|
f64 elapsed = frameEnd - frameStart;
|
|
f64 remaining = targetFrameTime - elapsed;
|
|
if (remaining > 0) {
|
|
WAPI.delay((u32)(remaining * 1000.0));
|
|
}
|
|
|
|
gfx_end_frame();
|
|
}
|
|
|
|
void audio_shutdown(void) {
|
|
audio_custom_shutdown();
|
|
if (audio_api) {
|
|
if (audio_api->shutdown) audio_api->shutdown();
|
|
audio_api = NULL;
|
|
}
|
|
}
|
|
|
|
void game_deinit(void) {
|
|
if (gGameInited) { configfile_save(configfile_name()); }
|
|
controller_shutdown();
|
|
audio_custom_shutdown();
|
|
audio_shutdown();
|
|
network_shutdown(true, true, false, false);
|
|
smlua_text_utils_shutdown();
|
|
smlua_shutdown();
|
|
smlua_audio_custom_deinit();
|
|
mods_shutdown();
|
|
djui_shutdown();
|
|
gfx_shutdown();
|
|
gGameInited = false;
|
|
}
|
|
|
|
void game_exit(void) {
|
|
LOG_INFO("exiting cleanly");
|
|
game_deinit();
|
|
exit(0);
|
|
}
|
|
|
|
void* main_game_init(UNUSED void* dummy) {
|
|
// load language
|
|
if (!djui_language_init(configLanguage)) { snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); }
|
|
|
|
LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading"));
|
|
dynos_gfx_init();
|
|
enable_queued_dynos_packs();
|
|
sync_objects_init_system();
|
|
|
|
if (gCLIOpts.network != NT_SERVER && !gCLIOpts.skipUpdateCheck) {
|
|
check_for_updates();
|
|
}
|
|
|
|
LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading ROM Assets"));
|
|
rom_assets_load();
|
|
smlua_text_utils_init();
|
|
|
|
mods_init();
|
|
enable_queued_mods();
|
|
LOADING_SCREEN_MUTEX(
|
|
gCurrLoadingSegment.percentage = 0;
|
|
loading_screen_set_segment_text("Starting Game");
|
|
);
|
|
|
|
audio_init();
|
|
sound_init();
|
|
network_player_init();
|
|
mumble_init();
|
|
|
|
gGameInited = true;
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// handle terminal arguments
|
|
if (!parse_cli_opts(argc, argv)) { return 0; }
|
|
|
|
#if defined(RAPI_DUMMY) || defined(WAPI_DUMMY)
|
|
gCLIOpts.headless = true;
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
// handle Windows console
|
|
if (gCLIOpts.console || gCLIOpts.headless) {
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
} else {
|
|
FreeConsole();
|
|
freopen("NUL", "w", stdout);
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
if (gCLIOpts.savePath[0]) {
|
|
char portable_path[SYS_MAX_PATH] = {};
|
|
sys_windows_short_path_from_mbs(portable_path, SYS_MAX_PATH, gCLIOpts.savePath);
|
|
fs_init(portable_path);
|
|
} else {
|
|
fs_init(sys_user_path());
|
|
}
|
|
#else
|
|
fs_init(gCLIOpts.savePath[0] ? gCLIOpts.savePath : sys_user_path());
|
|
#endif
|
|
|
|
#if !defined(RAPI_DUMMY) && !defined(WAPI_DUMMY)
|
|
if (gCLIOpts.headless) {
|
|
memcpy(&WAPI, &gfx_dummy_wm_api, sizeof(struct GfxWindowManagerAPI));
|
|
memcpy(&RAPI, &gfx_dummy_renderer_api, sizeof(struct GfxRenderingAPI));
|
|
}
|
|
#endif
|
|
|
|
configfile_load();
|
|
|
|
legacy_folder_handler();
|
|
|
|
// create the window almost straight away
|
|
if (!gGfxInited) {
|
|
gfx_init(&WAPI, &RAPI, TITLE);
|
|
WAPI.set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up,
|
|
keyboard_on_text_input, keyboard_on_text_editing);
|
|
WAPI.set_scroll_callback(mouse_on_scroll);
|
|
}
|
|
|
|
// render the rom setup screen
|
|
if (!main_rom_handler()) {
|
|
#ifdef LOADING_SCREEN_SUPPORTED
|
|
if (!gCLIOpts.hideLoadingScreen) {
|
|
render_rom_setup_screen(); // holds the game load until a valid rom is provided
|
|
} else
|
|
#endif
|
|
{
|
|
printf("ERROR: could not find valid vanilla us sm64 rom in game's user folder\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// start the thread for setting up the game
|
|
#ifdef LOADING_SCREEN_SUPPORTED
|
|
bool threadSuccess = false;
|
|
if (!gCLIOpts.hideLoadingScreen && !gCLIOpts.headless) {
|
|
if (init_thread_handle(&gLoadingThread, main_game_init, NULL, NULL, 0) == 0) {
|
|
render_loading_screen(); // render the loading screen while the game is setup
|
|
threadSuccess = true;
|
|
destroy_mutex(&gLoadingThread);
|
|
}
|
|
}
|
|
if (!threadSuccess)
|
|
#endif
|
|
{
|
|
main_game_init(NULL); // failsafe incase threading doesn't work
|
|
}
|
|
|
|
// initialize sm64 data and controllers
|
|
thread5_game_loop(NULL);
|
|
|
|
// initialize sound outside threads
|
|
if (gCLIOpts.headless) audio_api = &audio_null;
|
|
#if defined(AAPI_SDL1) || defined(AAPI_SDL2)
|
|
if (!audio_api && audio_sdl.init()) audio_api = &audio_sdl;
|
|
#endif
|
|
if (!audio_api) audio_api = &audio_null;
|
|
|
|
// Initialize the audio thread if possible.
|
|
// init_thread_handle(&gAudioThread, audio_thread, NULL, NULL, 0);
|
|
|
|
#ifdef LOADING_SCREEN_SUPPORTED
|
|
loading_screen_reset();
|
|
#endif
|
|
|
|
// initialize djui
|
|
djui_init();
|
|
djui_unicode_init();
|
|
djui_init_late();
|
|
djui_console_message_dequeue();
|
|
|
|
show_update_popup();
|
|
|
|
// initialize network
|
|
if (gCLIOpts.network == NT_CLIENT) {
|
|
network_set_system(NS_SOCKET);
|
|
snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", gCLIOpts.joinIp);
|
|
snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gCLIOpts.joinIp);
|
|
configJoinPort = gCLIOpts.networkPort;
|
|
network_init(NT_CLIENT, false);
|
|
} else if (gCLIOpts.network == NT_SERVER || gCLIOpts.coopnet) {
|
|
if (gCLIOpts.network == NT_SERVER) {
|
|
configNetworkSystem = NS_SOCKET;
|
|
configHostPort = gCLIOpts.networkPort;
|
|
} else {
|
|
configNetworkSystem = NS_COOPNET;
|
|
snprintf(configPassword, MAX_CONFIG_STRING, "%s", gCLIOpts.coopnetPassword);
|
|
}
|
|
|
|
// horrible, hacky fix for mods that access marioObj straight away
|
|
// best fix: host with the standard main menu method
|
|
static struct Object sHackyObject = { 0 };
|
|
gMarioStates[0].marioObj = &sHackyObject;
|
|
|
|
extern void djui_panel_do_host(bool reconnecting, bool playSound);
|
|
djui_panel_do_host(NULL, false);
|
|
} else {
|
|
network_init(NT_NONE, false);
|
|
}
|
|
|
|
// main loop
|
|
while (true) {
|
|
debug_context_reset();
|
|
CTX_BEGIN(CTX_TOTAL);
|
|
WAPI.main_loop(produce_one_frame);
|
|
#ifdef DISCORD_SDK
|
|
discord_update();
|
|
#endif
|
|
mumble_update();
|
|
#ifdef DEBUG
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
#endif
|
|
CTX_END(CTX_TOTAL);
|
|
|
|
#ifdef DEVELOPMENT
|
|
djui_ctx_display_update();
|
|
#endif
|
|
djui_lua_profiler_update();
|
|
}
|
|
|
|
return 0;
|
|
}
|