#include "Engine.h"
#include <atomic>
#include <condition_variable>
#include <cstring>
#include <cstdlib>
#include <filesystem>
#include <chrono>
#include <map>
#include <mutex>
#include <thread>
#include <fast/interpreter.h>
#include <libultraship.h>
#ifdef _WIN32
#include <windows.h>
#include <timeapi.h>
#pragma comment(lib, "winmm.lib")
#endif
#include <SDL2/SDL.h>
#include "DevTools/ThreadWatchdog.h"
#include "GameStatus.h"
#include "Interpolation/FrameInterpolation.h"
#include "Nametag/Nametag.h"
#include "Network/Anchor/Anchor.h"
#include "OS/OS.h"
#include "Patches/Patches.h"
#include "ShipUtils.h"
#include "ShipInit.hpp"
#include "src/port/Enhancements/Events/Hooks/Events.h"
#include "UI/LighthouseModMenuWindow.h"
extern "C" {
#include "enums.h"
#include "core1/core1.h"
#include "core1/main.h"
#include "core1/thread5.h"
void viMgr_entry(void* arg);
void thread5_entry(void* arg);
void audioManagerThread_entry(void* arg);
void core1_15B30_sendMesg3ToRenderThread(void);
OSMesgQueue* thread5_getTaskQueue(void);
OSMesgQueue* thread5_getSyncQueue(void);
}
namespace {
std::atomic<bool> sGameThreadDone{ false };
std::thread sGameThread;
thread_local bool tIsGameThread = false;
struct InterpPair {
int prev = -1;
int curr = -1;
bool should = false;
uint64_t serial = 0;
};
std::mutex sInterpMutex;
std::map<void*, InterpPair> sTaskInterp;
uint64_t sInterpSerial = 0;
constexpr uint64_t kInterpStaleAfter = 4;
std::mutex sSvcMutex;
std::condition_variable sSvcCv;
void (*sSvcFn)(void*) = nullptr;
void* sSvcArg = nullptr;
std::atomic<bool> sShutdownRequested{ false };
int sTitleMap = 0;
void DrainRenderService() {
std::unique_lock<std::mutex> lock(sSvcMutex);
if (sSvcFn != nullptr) {
auto* fn = sSvcFn;
void* arg = sSvcArg;
lock.unlock();
fn(arg);
lock.lock();
sSvcFn = nullptr;
sSvcCv.notify_all();
}
}
bool OnGameThread() {
return tIsGameThread;
}
}
extern "C" void port_thread5_onSubmit(void* taskData) {
if (!OnGameThread() || (uintptr_t)taskData < 100) {
return;
}
struct ucode_task_data_s* task = (struct ucode_task_data_s*)taskData;
if (task->task_type != UCODE_TASK_TYPE_F3DEX && task->task_type != UCODE_TASK_TYPE_L3DEX) {
return;
}
InterpPair pair;
FrameInterpolation_GetRecordingPair(&pair.prev, &pair.curr, &pair.should);
FrameInterpolation_ClaimPair(pair.prev, pair.curr);
FrameInterpolation_StopRecord();
Nametag::SubmitFrame(task->data_ptr);
std::lock_guard<std::mutex> lock(sInterpMutex);
pair.serial = ++sInterpSerial;
auto [it, inserted] = sTaskInterp.emplace(task->data_ptr, pair);
if (!inserted) {
FrameInterpolation_ReleasePair(it->second.prev, it->second.curr);
it->second = pair;
}
for (auto stale = sTaskInterp.begin(); stale != sTaskInterp.end();) {
if (stale->second.serial + kInterpStaleAfter < pair.serial) {
FrameInterpolation_ReleasePair(stale->second.prev, stale->second.curr);
stale = sTaskInterp.erase(stale);
} else {
++stale;
}
}
}
namespace {
void RenderTask(void* dlStart) {
InterpPair pair;
{
std::lock_guard<std::mutex> lock(sInterpMutex);
auto it = sTaskInterp.find(dlStart);
if (it != sTaskInterp.end()) {
pair = it->second;
sTaskInterp.erase(it);
}
}
FrameInterpolation_BeginRenderPass(pair.prev, pair.curr, pair.should);
Nametag::BeginRenderPass(dlStart, pair.should);
GameEngine::ProcessGfxCommands((Gfx*)dlStart);
FrameInterpolation_ReleasePair(pair.prev, pair.curr);
}
int ServiceRcp() {
OSTask* task = OS_SpTakePendingTask();
if (task == nullptr) {
return 0;
}
RenderTask(task->t.data_ptr);
OS_SendEventMesg(OS_EVENT_SP);
OS_SendEventMesg(OS_EVENT_DP);
return 1;
}
void EnableThread5() {
OS_EnableThreadEntry((void*)thread5_entry);
OS_SetQueueBlocking(thread5_getTaskQueue(), 1);
OS_SetQueueBlocking(thread5_getSyncQueue(), 1);
OS_EnableThreadEntry((void*)pfsManager_entry);
OS_SetQueueBlocking(pfsManager_getFrameMesgQ(), 1);
OS_EnableThreadEntry((void*)audioManagerThread_entry);
OS_SetQueueBlocking(audioManager_getFrameMesgQueue(), 1);
OS_SetQueueBlocking(audioManager_getReplyMesgQueue(), 1);
}
}
static void RegisterThread5MapSync_Init() {
COND_HOOK(OnMapLoad, EVENT_PRIORITY_HIGH, true, [](IEvent* event) {
(void)event;
port_pipelineSyncPoint();
});
}
static RegisterShipInitFunc sThread5MapSyncInit(RegisterThread5MapSync_Init);
extern "C" int port_renderServicePending(void) {
return sSvcFn != nullptr;
}
extern "C" void port_runOnRenderThread(void (*fn)(void*), void* arg) {
if (!OnGameThread()) {
fn(arg);
return;
}
std::unique_lock<std::mutex> lock(sSvcMutex);
auto done = [] { return sSvcFn == nullptr || sShutdownRequested.load(std::memory_order_acquire); };
if (sShutdownRequested.load(std::memory_order_acquire)) {
return;
}
sSvcCv.wait(lock, done);
if (sShutdownRequested.load(std::memory_order_acquire)) {
return;
}
sSvcFn = fn;
sSvcArg = arg;
sSvcCv.wait(lock, done);
}
extern "C" void port_pipelineSyncPoint(void) {
if (OnGameThread()) {
core1_15B30_sendMesg3ToRenderThread();
}
}
static bool sFrameRendered = false;
extern "C" void Graphics_PushFrame(Gfx* data) {
(void)data;
sFrameRendered = true;
}
void push_frame() {
static int sTitleCounter = 0;
sFrameRendered = false;
if (IsInlineModExtractionBusy()) {
SDL_Delay(16);
return;
}
GameEngine::Instance->StartFrame();
port_animVtx_beginTick();
const bool recordInterpolation = GameEngine::IsInterpolationEnabled();
GameEngine::SetInterpolationRecorded(recordInterpolation);
if (recordInterpolation) {
FrameInterpolation_StartRecord();
}
mainLoop();
if (recordInterpolation) {
FrameInterpolation_StopRecord();
}
if (sFrameRendered) {
port_tickDemoAudioHold();
}
if (++sTitleCounter >= 30) {
sTitleCounter = 0;
sTitleMap = gsworld_getMap();
port_runOnRenderThread([](void*) { port_setWindowTitle(sTitleMap); }, nullptr);
}
if (!sFrameRendered) {
SDL_Delay(33);
}
}
#ifdef __GNUC__
#define SDL_main main
#endif
int SDL_main(int argc, char* argv[]) {
#ifdef _WIN32
timeBeginPeriod(1);
#endif
std::error_code ec;
const char* shipHome = std::getenv("SHIP_HOME");
const char* appImage = std::getenv("APPIMAGE");
if (shipHome != nullptr && shipHome[0] != '\0') {
std::filesystem::current_path(shipHome, ec);
} else if (appImage != nullptr && appImage[0] != '\0') {
std::filesystem::current_path(std::filesystem::path(appImage).parent_path(), ec);
} else {
std::string base = Ship::Context::GetAppBundlePath();
if (!base.empty() && base != ".") {
std::filesystem::current_path(base, ec);
}
}
GameEngine::Create(argc, argv);
OS_EnableThreadEntry((void*)viMgr_entry);
EnableThread5();
core1_init();
ThreadWatchdog_Start();
sGameThread = std::thread([] {
tIsGameThread = true;
while (WindowIsRunning()) {
ThreadWatchdog_Beat(WATCHDOG_GAME_TICK);
push_frame();
}
sGameThreadDone.store(true);
});
while (WindowIsRunning() || !sGameThreadDone.load()) {
ThreadWatchdog_Beat(WATCHDOG_MAIN_LOOP);
port_noteMainLoopAlive();
Ship::Context::GetRawInstance()->GetWindow()->HandleEvents();
OS_SiService();
if (IsInlineModExtractionBusy()) {
GameEngine::Instance->RenderGuiFrame();
SDL_Delay(16);
continue;
}
DrainRenderService();
if (!ServiceRcp()) {
if (ThreadWatchdog_IsStalled(WATCHDOG_GAME_TICK)) {
GameEngine::Instance->RenderGuiFrame();
SDL_Delay(16);
continue;
}
SDL_Delay(1);
}
}
OS_RequestThreadExit();
{
std::lock_guard<std::mutex> lock(sSvcMutex);
sShutdownRequested.store(true, std::memory_order_release);
sSvcFn = nullptr;
}
sSvcCv.notify_all();
OS_BeginShutdown();
if (sGameThread.joinable()) {
sGameThread.join();
}
OS_JoinDecompThreads();
ThreadWatchdog_Stop();
OS_StopViTicker();
OS_StopTimerWorker();
#ifdef USE_NETWORKING
Anchor::GetInstance()->Disable();
SDLNet_Quit();
#endif
#ifdef _WIN32
timeEndPeriod(1);
#endif
GameEngine::Instance->Destroy();
GameEngine::RelaunchIfRequested(argc, argv);
return 0;
}