#include "..\munga_l4\mungal4.h" #pragma hdrstop #include "rpl4console.h" #include "rpl4fe.h" #include "..\munga\appmgr.h" #include "..\munga\appmsg.h" #include "..\rp\rpcnsl.h" #include "..\munga_l4\l4app.h" #include "..\munga_l4\l4net.h" //######################################################################## // The local console runs on ITS OWN THREAD, like the real console: it // stays alive across the whole session, owns the mission clock, and // raises the stop request when the selected length expires. The game // thread's per-frame tick is the only place engine calls happen - it // reports state transitions to the console thread and executes the // requested StopMissionMessage dispatch (the engine is single // threaded; cross-thread dispatch is not safe). // // Results flow in through gConsoleScoreSink (RP layer): the same final // scores RPPlayer sent the arcade console at mission end. //######################################################################## namespace { enum ConsolePhase { PhaseWaiting = 0, // waiting for the mission to start running PhaseRunning, // mission running, console thread watching the clock PhaseStopped // stop dispatched, waiting for teardown }; ConsolePhase gPhase = PhaseWaiting; int gMissionSeconds = 0; Application *gWatchedApp = NULL; HANDLE gConsoleThread = NULL; // shared with the console thread volatile LONG gMissionRunning = 0; volatile LONG gStopRequested = 0; volatile LONG gShuttingDown = 0; volatile LONG gRunStartTick = 0; volatile LONG gLengthMs = 0; // collected mission results (this session's last race) enum { maxResults = 16 }; struct FinalScore { int hostID; int score; }; FinalScore gResults[maxResults]; int gResultCount = 0; //--------------------------------------------------------------- // The console thread: the mission clock lives here //--------------------------------------------------------------- DWORD WINAPI ConsoleThreadProc(LPVOID) { while (!gShuttingDown) { Sleep(250); if (gMissionRunning && !gStopRequested) { LONG length_ms = gLengthMs; if (length_ms > 0 && (LONG)(GetTickCount() - (DWORD) gRunStartTick) >= length_ms) { InterlockedExchange(&gStopRequested, 1); } } } return 0; } //--------------------------------------------------------------- // Final-score intake (called on the game thread from RPPlayer's // mission-ending path, via the RP-layer sink) //--------------------------------------------------------------- void CollectFinalScore(int host_ID, int score) { if (gResultCount < maxResults) { gResults[gResultCount].hostID = host_ID; gResults[gResultCount].score = score; ++gResultCount; } DEBUG_STREAM << "LocalConsole: final score, host " << host_ID << " = " << score << "\n" << std::flush; } //--------------------------------------------------------------- // The game-thread tick: state reporting + engine-safe execution //--------------------------------------------------------------- void ConsoleTick() { if (application == NULL) { return; } if (gPhase != PhaseWaiting && application != gWatchedApp) { return; } int state = application->GetApplicationState(); switch (gPhase) { case PhaseWaiting: if (state == Application::RunningMission) { gPhase = PhaseRunning; gWatchedApp = application; gResultCount = 0; InterlockedExchange(&gRunStartTick, (LONG) GetTickCount()); InterlockedExchange(&gStopRequested, 0); InterlockedExchange(&gMissionRunning, 1); DEBUG_STREAM << "LocalConsole: mission running, length " << gMissionSeconds << "s\n" << std::flush; } break; case PhaseRunning: if (state != Application::RunningMission) { // mission ended some other way (pilot exit etc.) InterlockedExchange(&gMissionRunning, 0); gPhase = PhaseStopped; } else if (gStopRequested) { //----------------------------------------------------- // The console clock expired: end the race exactly the // way the arcade console did, otherwise the mission // clock rolls past 00:00 and counts up forever. //----------------------------------------------------- DEBUG_STREAM << "LocalConsole: time expired - stopping mission\n" << std::flush; InterlockedExchange(&gMissionRunning, 0); Application::StopMissionMessage message(0); application->Dispatch(&message); gPhase = PhaseStopped; } break; case PhaseStopped: // The application tears itself down after a stop (arcade // pods were relaunched per mission). WinMain's race loop // asks MissionCompleted() and cycles back to the setup // screen in the same process. break; } } } void RPL4LocalConsole_Install(int mission_seconds) { // debug: L4CONSOLELEN overrides the mission length (test races) const char *override_string = getenv("L4CONSOLELEN"); if (override_string != NULL && atoi(override_string) > 0) { mission_seconds = atoi(override_string); DEBUG_STREAM << "LocalConsole: L4CONSOLELEN override, " << mission_seconds << "s\n" << std::flush; } gMissionSeconds = mission_seconds; InterlockedExchange(&gLengthMs, (LONG) mission_seconds * 1000); gPhase = PhaseWaiting; gWatchedApp = NULL; // game-thread execution point gPerFrameHook = &ConsoleTick; // results intake from the RP layer gConsoleScoreSink = &CollectFinalScore; // the console itself lives on its own thread, like the real one if (gConsoleThread == NULL) { gConsoleThread = CreateThread( NULL, 0, ConsoleThreadProc, NULL, 0, NULL); } DEBUG_STREAM << "LocalConsole: installed (length " << mission_seconds << "s, console thread " << (gConsoleThread != NULL ? "up" : "FAILED") << ")\n" << std::flush; } Logical RPL4LocalConsole_MissionCompleted() { return gPhase == PhaseStopped; } int RPL4LocalConsole_ResultCount() { return gResultCount; } Logical RPL4LocalConsole_GetResult(int index, int *host_ID, int *score) { if (index < 0 || index >= gResultCount) { return False; } *host_ID = gResults[index].hostID; *score = gResults[index].score; return True; }