Files
RP412/MUNGA/APP.cpp
T
CydandClaude Fable 5 af52476603 The pod can drive a scripted lap
RP412INPUTSCRIPT names a timeline file - one row per change, throttle,
stick X and Y, pedals, held until the next row's time - and the pod
drives it instead of listening to the controls. Times are SIMULATION
seconds from the green light, evaluated per step in the one place every
mapper funnels through (VTVControlsMapper::InterpretControls), so the
same script is the same lap at any frame rate. Rows hold rather than
interpolate on purpose: interpolation would sample differently at
different physics rates, and nothing on this path is allowed to.

The script shares the green-light anchor with RP412PHYSTRACE - its
clock starts at the instant the vehicle is stopped dead - because a
timeline that starts when the loader happens to finish is a different
lap every run.

A race is only deterministic if somebody DRIVES it, and a human cannot
drive the same lap twice. The first scripted lap - full throttle, a
steer, a crash at speed - earned the harness immediately:

- The drive, the crash, the death and the respawn teleport were all
  BIT-EXACT between identical runs, through t=13.5. Collisions with
  world geometry and the damage path are step-deterministic, which is
  better news than the code reading suggested.

- The first divergence is the step AFTER the respawn: the DropZoneReply
  that stands a dead pod back up is posted at wall-clock Now()+1.0
  (RPPLAYER.cpp), so the reset lands on a different sim step every run
  and everything after is time-shifted. The crash is deterministic; the
  RECOVERY is not. That is the next fix, and it is now a measurement,
  not a theory.

Values are clamped at load, once and visibly, so a script asking for
throttle 2.0 cannot trip the mapper's own range Verifies. Off unless
the environment names a file; it would be a cheat in a real race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 21:03:03 -05:00

1977 lines
50 KiB
C++

#include "registry.h"
#include "renderer.h"
#include "controls.h"
#include "update.h"
#include "mission.h"
#include "player.h"
#include "director.h"
#include "appmgr.h"
#include "audrend.h"
#include "vidrend.h"
#include "gaugrend.h"
#include "hostmgr.h"
#include "interest.h"
#include "nttmgr.h"
#include "apptask.h"
#include "console.h"
#include "appmsg.h"
#include "evtstat.h"
#include "inputscript.h"
#if defined(TRACE_FOREGROUND_PROCESSING)
BitTrace Foreground_Processing("Foreground Processing");
#endif
#if defined(TRACE_UPDATE_MANAGER)
BitTrace Update_Manager("Update Manager");
#endif
#if defined(TRACE_RENDERER_MANAGER)
BitTrace Renderer_Manager("Renderer Manager");
#endif
Application *application = NULL;
int Exit_Code = 0;
Logical Application::suppressGauges = False;
//#############################################################################
//########################### Application ###############################
//#############################################################################
//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
Application::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(Application, StateQuery),
MESSAGE_ENTRY(Application, CheckLoad),
MESSAGE_ENTRY(Application, RunMission),
MESSAGE_ENTRY(Application, StopMission),
MESSAGE_ENTRY(Application, ResumeMission),
MESSAGE_ENTRY(Application, SuspendMission),
MESSAGE_ENTRY(Application, KeyCommand),
MESSAGE_ENTRY(Application, LoadMission),
MESSAGE_ENTRY(Application, AbortMission)
};
Application::MessageHandlerSet& Application::GetMessageHandlers()
{
static Application::MessageHandlerSet messageHandlers(ELEMENTS(Application::MessageHandlerEntries), Application::MessageHandlerEntries, NetworkClient::GetMessageHandlers());
return messageHandlers;
}
//#############################################################################
// Virtual Data support
//
Derivation* Application::GetClassDerivations()
{
static Derivation classDerivations(NetworkClient::GetClassDerivations(), "Application");
return &classDerivations;
}
Application::SharedData
Application::DefaultData(
Application::GetClassDerivations(),
Application::GetMessageHandlers()
);
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
Application::TestInstance() const
{
if (!IsDerivedFrom(*GetClassDerivations()))
{
return False;
}
Check(&applicationState);
if (eventQueue)
{
Check(eventQueue);
}
if (networkManager)
{
Check(networkManager);
}
if (entityManager)
{
Check(entityManager);
}
if (registry)
{
Check(registry);
}
if (hostManager)
{
Check(hostManager);
}
if (interestManager)
{
Check(interestManager);
}
if (updateManager)
{
Check(updateManager);
}
if (rendererManager)
{
Check(rendererManager);
}
if (controlsManager)
{
Check(controlsManager);
}
if (intercomManager)
{
Check(intercomManager);
}
if (resourceFile)
{
Check(resourceFile);
}
if (viewpointEntity)
{
Check(viewpointEntity);
}
if (currentMission)
{
Check(currentMission);
}
if (backgroundTasks)
{
Check(backgroundTasks);
}
if (audioRenderer)
{
Check(audioRenderer);
}
if (videoRenderer)
{
Check(videoRenderer);
}
if (gaugeRenderer)
{
Check(gaugeRenderer);
}
return True;
}
//
//#############################################################################
// Application
//#############################################################################
//
Application::Application(
ResourceFile *resource_file,
ApplicationID application_ID,
ClassID class_ID,
SharedData &shared_data
):
NetworkClient(class_ID, shared_data, ApplicationClientID),
applicationState(ApplicationStateCount)
{
Check(&shared_data);
Check(resource_file);
applicationID = application_ID;
//
// Create the event queue
//
eventQueue = GeneralEventQueue::Make(
EVENT_PRIORITIES_COUNT,
"MUNGA Event Count"
);
Check(eventQueue);
//
// Remember the resource file
//
resourceFile = resource_file;
missionPlayer = NULL;
//
// NULL managers not yet created
//
networkManager = NULL;
registry = NULL;
controlsManager = NULL;
intercomManager = NULL;
backgroundTasks = NULL;
interestManager = NULL;
audioRenderer = NULL;
videoRenderer = NULL;
gaugeRenderer = NULL;
//
// NULL pointers to other objects not yet created
//
viewpointEntity = NULL;
currentMission = NULL;
//
// Create base level managers
//
entityManager = new EntityManager;
Register_Object(entityManager);
hostManager = new HostManager;
Register_Object(hostManager);
updateManager = new UpdateManager;
Register_Object(updateManager);
rendererManager = new RendererManager;
Register_Object(rendererManager);
backgroundTasks = new BackgroundTasks;
Register_Object(backgroundTasks);
//
// Set up game state
//
executeFrames = False;
applicationState.SetState(InitializingState);
currentMission = NULL;
secondsRemainingInGame = 0.0f;
spoolFile = NULL;
routePacketFinished = False;
lastCreationMessage = Now();
//
// HACK - Init analysis bits
//
#if defined(TRACE_ON)
trace_manager.ResetTraces();
#endif
}
//
//#############################################################################
// GetApplicationManager
//#############################################################################
//
ApplicationManager*
Application::GetApplicationManager()
{
PlugIteratorOf<ApplicationManager*> manager_link(this);
ApplicationManager *mgr;
while ((mgr = manager_link.ReadAndNext()) != NULL)
{
if (mgr->GetClassID() == ApplicationManagerClassID)
{
return mgr;
}
}
return NULL;
}
Scalar
Application::GetApplicationLoopFrameRate()
{
ApplicationManager *mgr = GetApplicationManager();
Check(mgr);
return mgr->GetFrameRate();
}
//
//#############################################################################
// GetMissionElapsed
//#############################################################################
//
Scalar
Application::GetMissionElapsed()
{
Check(this);
//
//--------------------------------------------------------------------------
// gameStarted is only ever stamped by RunMissionMessageHandler, so before
// the race it is uninitialized - and entities that are pre-runnable do get
// performed before then. Answer zero until the clock actually exists.
//--------------------------------------------------------------------------
//
if (
GetApplicationState() != RunningMission
&& GetApplicationState() != EndingMission
)
{
return 0.0f;
}
Scalar elapsed = Now() - gameStarted;
return (elapsed > 0.0f) ? elapsed : 0.0f;
}
//
//#############################################################################
// Initialize
//#############################################################################
//
void
Application::Initialize()
{
Check(this);
//
//----------------------------
// Create the interest Manager
//----------------------------
//
interestManager = MakeInterestManager();
Register_Object(interestManager);
//
//--------------------------------------------------------------------------
// Create the network manager
//--------------------------------------------------------------------------
//
networkManager = MakeNetworkManager();
Register_Object(networkManager);
//
//--------------------------------------------------------------------------
// Create the registry, load static object streams
//--------------------------------------------------------------------------
//
registry = MakeRegistry();
if (registry)
{
Register_Object(registry);
registry->LoadStaticObjectStreamResource();
}
//
//--------------------------------------------------------------------------
// Create the mode manager
//--------------------------------------------------------------------------
//
modeManager = MakeModeManager();
Register_Object(modeManager);
//
//--------------------------------------------------------------------------
// Create the controls manager
//--------------------------------------------------------------------------
//
controlsManager = MakeControlsManager();
Register_Object(controlsManager);
//
//--------------------------------------------------------------------------
// Create the intercom manager
//--------------------------------------------------------------------------
//
intercomManager = MakeIntercomManager();
Register_Object(intercomManager);
//
//--------------------------------------------------------------------------
// Add background tasks
//--------------------------------------------------------------------------
//
LoadBackgroundTasks();
executeFrames = True;
applicationState.SetState(WaitingForEgg);
//
//--------------------------------------------------------------------------
// Create the audio renderer
//--------------------------------------------------------------------------
//
Verify(audioRenderer == NULL);
if ((audioRenderer = MakeAudioRenderer()) != NULL)
{
Register_Object(audioRenderer);
audioRenderer->Initialize();
}
//
//--------------------------------------------------------------------------
// Create the video renderer
//--------------------------------------------------------------------------
//
Verify(videoRenderer == NULL);
if ((videoRenderer = MakeVideoRenderer()) != NULL)
{
Register_Object(videoRenderer);
}
//
//--------------------------------------------------------------------------
// Create the gauge renderer
//--------------------------------------------------------------------------
//
Verify(gaugeRenderer == NULL);
if (Application::GetVideoRenderer() != NULL)
{
int *secondaryIndex = Application::GetVideoRenderer()->GetSecondaryIndex();
int *aux1Index = Application::GetVideoRenderer()->GetAux1Index();
int *aux2Index = Application::GetVideoRenderer()->GetAux2Index();
if ((gaugeRenderer = MakeGaugeRenderer(secondaryIndex, aux1Index, aux2Index)) != NULL)
{
Register_Object(gaugeRenderer);
}
}
}
//
//#############################################################################
// LoadBackgroundTasks
//#############################################################################
//
void
Application::LoadBackgroundTasks()
{
Check(this);
ApplicationTask *application_task;
application_task = new RoutePacketTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new ProcessEventTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new AudioRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new GaugeRendererTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new NetworkManagerTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new CompleteCyclesTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
application_task = new FryDeathRowTask;
Register_Object(application_task);
backgroundTasks->AddTask(application_task);
}
//
//#############################################################################
// MakeInterestManager
//#############################################################################
//
InterestManager*
Application::MakeInterestManager()
{
return new InterestManager;
}
//
//#############################################################################
// MakeNetworkManager
//#############################################################################
//
NetworkManager*
Application::MakeNetworkManager()
{
return new NetworkManager(NetworkManager::DefaultData);
}
//
//#############################################################################
// MakeRegistry
//#############################################################################
//
Registry*
Application::MakeRegistry()
{
Fail("Application::MakeRegistry - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeModeManager
//#############################################################################
//
ModeManager*
Application::MakeModeManager()
{
Fail("Application::MakeModeManager - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeControlsManager
//#############################################################################
//
ControlsManager*
Application::MakeControlsManager()
{
Fail("Application::MakeControlsManager - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeIntercomManager
//#############################################################################
//
IcomManager*
Application::MakeIntercomManager()
{
Fail("Application::MakeIntercomManager - Should never reach here");
return NULL;
}
//
//#############################################################################
// ExecuteForeground
//#############################################################################
//
Logical
Application::ExecuteForeground(
Time start_of_frame,
Scalar frame_duration
)
{
SET_FOREGROUND_PROCESSING();
Check(this);
Verify(application == this);
if (!executeFrames)
{
CLEAR_FOREGROUND_PROCESSING();
return executeFrames;
}
Time
frame_ticks;
frame_ticks = frame_duration;
//
//--------------------------------------------------------------------------
// Controls Manager
//
// Poll all devices, update all control variables.
//
// This is executed before the update manager so that the
// models have valid control values. It is not necessary for
// the controls to operate at the frame rate of this loop. If
// the controls manager can run run at a lower rate it can
// throttle itself internally.
//--------------------------------------------------------------------------
//
Check(controlsManager);
Time startControls = Now();
controlsManager->Execute();
Time endControls = Now();
if (GetApplicationState() == WaitingForEgg)
{
// since we're idling, let the renderers clear the screens
videoRenderer->ExecuteIdle();
CLEAR_FOREGROUND_PROCESSING();
return executeFrames;
}
//
//--------------------------------------------------------------------------
// Update Manager
//
// Execute replicants.
// Execute master entities if they are interesting here or
// elsewhere.
// Inform interest manager of possible interest zone change.
// If the master entity has provided and update message then
// send it to replicants.
//
// This is executed before the interest manager so that the
// interest manager can merge all interest zone changes before
// broadcasting interest arena deltas and building interesting
// entity lists.
//
// This is executed before the renderers so that the watchers
// have executed on the model and are ready for use by the
// renderers.
//--------------------------------------------------------------------------
//
SET_UPDATE_MANAGER();
Time startUpdate = Now();
Check(updateManager);
updateManager->Execute(start_of_frame);
Time endUpdate = Now();
//
//--------------------------------------------------------------------------
// RP412PHYSTRACE=1: the player's position, sampled on the SIMULATION's
// own clock rather than per frame.
//
// This is the acceptance test for decoupling physics from frame rate.
// Run the same egg at two frame rates and diff the traces: today they
// diverge, because the simulation advances by whatever the last frame
// happened to cost (SIMULATE.cpp, slice = till - lastPerformance), so a
// 30 fps machine integrates in 33 ms steps and a 144 fps machine in 7 ms
// ones and they are not the same race. Fixed-step them and the two
// traces have to agree.
//
// Sampled every 0.25 s of SIM time on purpose - sampling per frame would
// compare different instants and prove nothing.
//--------------------------------------------------------------------------
//
{
static int physTrace = -1;
if (physTrace < 0)
{
const char *setting = getenv("RP412PHYSTRACE");
physTrace = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
}
//
// The scripted-input harness shares this anchor: its clock has to
// start at the same instant the vehicle is stopped, or the script
// timeline shifts against the settling transient by however long
// the load happened to take.
//
if ((physTrace || RPInputScript_Active()) &&
GetApplicationState() == RunningMission)
{
static Logical traceStarted = False;
static Time traceOrigin;
static Scalar traceDue = (Scalar) 0;
if (!traceStarted)
{
traceStarted = True;
traceOrigin = start_of_frame;
traceDue = (Scalar) 0;
//
//----------------------------------------------------------
// Start the measurement from a known state, not merely a
// known place.
//
// The pod sits on its pad simulating while the mission
// loads, and a load is not the same length twice - two runs
// of the same egg reached the green light 776 steps in and
// 599 steps in. Same pad, same position, different VELOCITY,
// and a trajectory compared from there measures the loader,
// not the physics.
//
// So: stop the vehicle dead and put its clock on the same
// mark. Every run then starts from rest at the same instant
// and any difference that follows belongs to the simulation.
//
// Test scaffolding, and it only runs with the trace asked
// for - it would be a cheat in a real race.
//----------------------------------------------------------
//
Player *reset_player = GetMissionPlayer();
Entity *reset_vehicle =
(reset_player != NULL)
? reset_player->GetPlayerVehicle() : NULL;
if (reset_vehicle != NULL &&
reset_vehicle->IsDerivedFrom(*Mover::GetClassDerivations()))
{
Mover *reset_mover = (Mover *) reset_vehicle;
reset_mover->localVelocity = Motion::Identity;
reset_mover->localAcceleration = Motion::Identity;
//
// The clock is NOT touched. lastPerformance sits on the
// vehicle's own step grid and the trace reads that grid
// instead. The first version forced it to the frame
// timestamp, which knocked the vehicle off its grid by
// a random fraction of a step per run - and that read
// as physics drift when it was only ever measurement.
//
traceOrigin = reset_mover->GetLastPerformance();
// the script's t=0 is this same instant
RPInputScript_Arm(traceOrigin);
DEBUG_STREAM << "PhysTrace: vehicle stopped "
<< "at the green light\n" << std::flush;
}
}
if (physTrace)
{
//
// Sampled on the SIMULATION's clock - the vehicle's own
// lastPerformance, which advances in whole fixed steps - so two
// runs sample at identical step counts and their traces compare
// exactly. Frame time samples mid-step at whatever phase the
// frame happened to land on, which compares different instants
// and calls the difference physics.
//
Player *clock_player = GetMissionPlayer();
Entity *clock_vehicle =
(clock_player != NULL) ? clock_player->GetPlayerVehicle() : NULL;
Scalar elapsed =
(clock_vehicle != NULL)
? (Scalar)(clock_vehicle->GetLastPerformance() - traceOrigin)
: (Scalar)(start_of_frame - traceOrigin);
if (elapsed >= traceDue)
{
traceDue += (Scalar) 0.25;
Player *trace_player = GetMissionPlayer();
Entity *trace_vehicle =
(trace_player != NULL) ? trace_player->GetPlayerVehicle() : NULL;
if (trace_vehicle != NULL)
{
extern long gPhysicsStepsTaken;
char buffer[160];
sprintf(buffer,
"PhysTrace: t=%7.3f steps=%6ld pos %12.5f %12.5f %12.5f\n",
(double) elapsed,
gPhysicsStepsTaken,
(double) trace_vehicle->localOrigin.linearPosition.x,
(double) trace_vehicle->localOrigin.linearPosition.y,
(double) trace_vehicle->localOrigin.linearPosition.z);
DEBUG_STREAM << buffer << std::flush;
}
}
}
}
}
CLEAR_UPDATE_MANAGER();
//
//--------------------------------------------------------------------------
// Interest Manager
//
// Update the net interest arena.
// Calculate which interest zones should be released and which
// interest zones should be loaded
// Send a becoming uninteresting and becoming interesting
// message to the entity.
// Broadcast the interest arena change
//
// The renderer manager call, update interest origins will
// update only those interest origins required by the renderers
// executing this frame.
//
// This is executed before the renderer manager so that when
// the renderers execute they execute upon interest lists that
// are valid as of the end of a model/update frame.
//
// In theory the interest manager does not have to run every
// frame. If throttled less, this would cause fuzz at interest
// zone borders, which may or may not be acceptable depending
// on the size of the interest arena.
//--------------------------------------------------------------------------
//
Time startInterest = Now();
Check(rendererManager);
rendererManager->UpdateInterestOrigins(start_of_frame);
Check(interestManager);
interestManager->Execute();
Time endInterest = Now();
//
//--------------------------------------------------------------------------
// Renderer Manager
//
// Execute renderers.
// Get interest list from interest manager.
// Poll watchers.
//
// It is not necessary for all renderers to operate at the frame
// rate of this loop. If a renderer can run at a lower rate it
// does so via the renderer manager which will govern if a
// renderer executes this frame.
//--------------------------------------------------------------------------
//
SET_RENDERER_MANAGER();
Time startRender = Now();
Check(rendererManager);
rendererManager->Execute(start_of_frame, frame_ticks, frame_ticks);
Time endRender = Now();
CLEAR_RENDERER_MANAGER();
//
//--------------------------------------------------------------------------
// Intercom Manager
//--------------------------------------------------------------------------
//
Time startIntercom = Now();
Check(intercomManager);
intercomManager->Execute();
Time endIntercom = Now();
//
//--------------------------------------------------------------------------
// Execution statistics
//--------------------------------------------------------------------------
//
if (GetApplicationState() == RunningMission)
{
//
// Ask the console first: it owns the clock that actually ends the
// race, so this is the countdown the buzzer will agree with. Its
// own reckoning is the fallback for everything with no console of
// its own - see gMissionClockHook in APPMGR.h.
//
Scalar console_remaining;
if (gMissionClockHook != NULL &&
(*gMissionClockHook)(&console_remaining))
{
secondsRemainingInGame = console_remaining;
}
else
{
secondsRemainingInGame =
currentMission->GetGameLength() - (Now() - gameStarted);
}
}
routePacketFinished = False;
CLEAR_FOREGROUND_PROCESSING();
return executeFrames && !Exit_Code;
}
//
//#############################################################################
// ExecuteBackgroundTask
//#############################################################################
//
#define QUIET_TIME_OUT 3.0f
void
Application::ExecuteBackgroundTask()
{
Check(this);
//
//------------------------------------------------------------------------
// If we are processing the map's creation messages, don't allow any other
// processing to happen until nothing appears for the timeout period
//------------------------------------------------------------------------
//
if (GetApplicationState() == CreatingMission)
{
Check(networkManager);
if (networkManager->RoutePacket())
{
return;
}
if (ProcessOneEvent(DefaultEventPriority))
{
return;
}
if (ProcessOneEvent(LowEventPriority))
{
lastCreationMessage = Now();
}
else
{
Scalar wait = Now() - lastCreationMessage;
if (wait > QUIET_TIME_OUT)
{
applicationState.SetState(LoadingMission);
networkManager->Marker("MUNGA MARKER - Starting renderer load...\n");
networkManager->Mode(NetworkManager::UnreliableMode);
#if defined(LAB_ONLY)
DEBUG_STREAM << "Starting renderer load...\n" << std::flush;
#endif
}
}
return;
}
//
// If there exists a high priority event, execute it
//
if (ProcessOneEvent(HighEventPriority))
{
return;
}
//
// if
// we are not finished routing packets this frames or
// we are not running the game
// then
// attempt to route a network packet
// we are finished routing packets for this frame
//
if (
!routePacketFinished ||
applicationState.GetState() != RunningMission
)
{
Check(networkManager);
if (networkManager->RoutePacket())
{
return;
}
}
routePacketFinished = True;
//
// Execute lower priority tasks
//
Check(backgroundTasks);
backgroundTasks->Execute();
}
//
//#############################################################################
// Stop
//#############################################################################
//
void
Application::Stop()
{
Check(this);
//
// Dump analysis sample
//
#if defined(USE_TIME_ANALYSIS)
DEBUG_STREAM << "\nGame timing statistics:\n" << std::flush;
trace_manager.SnapshotTimingAnalysis(True);
#endif
#if defined(USE_TRACE_LOG)
trace_manager.SaveTraceLog("trace.log");
#endif
#if defined(USE_EVENT_STATISTICS)
event_statistics_manager.Report();
#endif
//
// Set state variables to end game status
//
executeFrames = False;
DEBUG_STREAM << std::flush << std::flush;
applicationState.SetState(StoppingMission);
}
//
//#############################################################################
// Shutdown
//#############################################################################
//
Logical
Application::Shutdown(int remainingApps)
{
Check(this);
//
//--------------------------------------------------------------------------
// Shutdown gauge renderer
//--------------------------------------------------------------------------
//
if (gaugeRenderer != NULL)
{
Check(gaugeRenderer);
gaugeRenderer->Shutdown();
gaugeRenderer->UnlinkFromEntity();
}
//
//--------------------------------------------------------------------------
// Shutdown video renderer
//--------------------------------------------------------------------------
//
if (videoRenderer != NULL)
{
Check(videoRenderer);
videoRenderer->Shutdown();
videoRenderer->UnlinkFromEntity();
}
//
//--------------------------------------------------------------------------
// Shutdown audio renderer
//--------------------------------------------------------------------------
//
if (audioRenderer != NULL)
{
Check(audioRenderer);
audioRenderer->Shutdown();
audioRenderer->UnlinkFromEntity();
}
//
//--------------------------------------------------------------------------
// Delete the viewpoint entity
//--------------------------------------------------------------------------
//
if (viewpointEntity != NULL)
{
Unregister_Object(viewpointEntity);
delete viewpointEntity;
viewpointEntity = NULL;
}
//
//--------------------------------------------------------------------------
// Shutdown the interest manager
//--------------------------------------------------------------------------
//
Check(interestManager);
interestManager->Shutdown();
//
//--------------------------------------------------------------------------
// Shutdown the host manager
//--------------------------------------------------------------------------
//
Check(hostManager);
hostManager->Shutdown();
//
//--------------------------------------------------------------------------
// Shutdown the network manager
//--------------------------------------------------------------------------
//
Check(networkManager);
networkManager->Shutdown();
//
//--------------------------------------------------
// Delete the current mission if it has been created
//--------------------------------------------------
//
if (currentMission)
{
Unregister_Object(currentMission);
delete currentMission;
currentMission = NULL;
}
//
//---------------------------------
// Allow the process to start again
//---------------------------------
//
executeFrames = True;
applicationState.SetState(WaitingForEgg);
#if 0
return !Exit_Code;
#else
return False;
#endif
}
//
//#############################################################################
// Terminate
//#############################################################################
//
void
Application::Terminate()
{
Check(this);
//
//--------------------------------------------------------------------------
// Delete the gauge renderer
//--------------------------------------------------------------------------
//
if (gaugeRenderer != NULL)
{
Unregister_Object(gaugeRenderer);
delete gaugeRenderer;
gaugeRenderer = NULL;
}
//
//--------------------------------------------------------------------------
// Delete the video renderer
//--------------------------------------------------------------------------
//
if (videoRenderer != NULL)
{
Unregister_Object(videoRenderer);
delete videoRenderer;
videoRenderer = NULL;
}
//
//--------------------------------------------------------------------------
// Delete the audio renderer
//--------------------------------------------------------------------------
//
if (audioRenderer != NULL)
{
Unregister_Object(audioRenderer);
delete audioRenderer;
audioRenderer = NULL;
}
//
//-----------------------------------------------------------------------
// Delete the intercom manager
//-----------------------------------------------------------------------
//
if (intercomManager != NULL)
{
Unregister_Object(intercomManager);
delete intercomManager;
intercomManager = NULL;
}
//
//-----------------------------------------------------------------------
// Delete the controls manager
//-----------------------------------------------------------------------
//
if (controlsManager != NULL)
{
Unregister_Object(controlsManager);
delete controlsManager;
controlsManager = NULL;
}
//
//------------------------
// Delete the mode manager
//------------------------
//
if (modeManager)
{
Unregister_Object(modeManager);
delete modeManager;
modeManager = NULL;
}
//
//-----------------------------------------------------------------------
// Delete the registry
//-----------------------------------------------------------------------
//
if (registry != NULL)
{
Unregister_Object(registry);
delete registry;
registry = NULL;
}
//
//-----------------------------------------------------------------------
// Delete the network manager
//-----------------------------------------------------------------------
//
if (networkManager != NULL)
{
Unregister_Object(networkManager);
delete networkManager;
networkManager = NULL;
}
//
//----------------------------
// Delete the interest manager
//----------------------------
//
if (interestManager != NULL)
{
Unregister_Object(interestManager);
delete interestManager;
interestManager = NULL;
}
}
//
//#############################################################################
// ~Application
//#############################################################################
//
Application::~Application()
{
//
// Verify that these managers have been deleted
//
Verify(interestManager == NULL);
Verify(networkManager == NULL);
Verify(registry == NULL);
Verify(controlsManager == NULL);
Verify(intercomManager == NULL);
Verify(audioRenderer == NULL);
Verify(videoRenderer == NULL);
Verify(gaugeRenderer == NULL);
//
// Verify that the mission has been deleted
//
Verify(currentMission == NULL);
//
// Verify that the viewpoint entity has been deleted
//
Verify(viewpointEntity == NULL);
//
// Destroy these managers
//
Unregister_Object(backgroundTasks);
delete backgroundTasks;
backgroundTasks = NULL;
Unregister_Object(rendererManager);
delete rendererManager;
rendererManager = NULL;
Unregister_Object(updateManager);
delete updateManager;
updateManager = NULL;
Unregister_Object(hostManager);
delete hostManager;
hostManager = NULL;
Unregister_Object(entityManager);
delete entityManager;
entityManager = NULL;
resourceFile = NULL;
//
// Destroy the event queue
// HACK - the event queue is transparently created as an array,
// therefore must be deleted as one
//
Unregister_Object(eventQueue);
delete[] eventQueue;
eventQueue = NULL;
}
//
//#############################################################################
// StateQueryMessageHandler
//#############################################################################
//
void
Application::StateQueryMessageHandler(
#if DEBUG_LEVEL>0
StateQueryMessage *message
#else
StateQueryMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == StateQueryMessageID);
//
//--------------------------------------------------------------------------
// Send the console our application state
//--------------------------------------------------------------------------
//
Host *console_host;
Check(GetHostManager());
if ((console_host = GetHostManager()->GetConsoleHost()) != NULL)
{
Check(console_host);
unsigned int appState = applicationState.GetState();
ConsoleApplicationStateResponseMessage
response_message(
0, // GetHostManager()->GetLocalHostID(),
applicationState.GetState(),
GetApplicationID()
);
SendMessage(
console_host->GetHostID(),
ConsoleClientID,
&response_message
);
}
}
//
//#############################################################################
// CreateMission
//#############################################################################
//
void
Application::CreateMission(NotationFile *egg_notation_file)
{
Check(this);
Check(egg_notation_file);
//
// Forget every peer's clock offset: the hosts in the next race are not
// the hosts in the last one, and a HostID gets reused.
//
NetClock_Reset();
//
//--------------------------------------------------------------------------
// Create mission from egg notation file
//--------------------------------------------------------------------------
//
currentMission = MakeMission(egg_notation_file, resourceFile);
Register_Object(currentMission);
secondsRemainingInGame = currentMission->GetGameLength();
//
//----------------------------------------------------------------------
// Now, start up the network connect process. The network must have at
// least established the local host before returning, so that the player
// data load can work
//----------------------------------------------------------------------
//
NetworkManager *net_mgr = GetNetworkManager();
Check(net_mgr);
net_mgr->StartConnecting(currentMission);
currentMission->SetPlayerData(egg_notation_file);
InterestManager *interest_mgr = GetInterestManager();
Check(interest_mgr);
interest_mgr->LoadInterestArenas(currentMission);
}
//
//#############################################################################
// MakeMission
//#############################################################################
//
Mission*
Application::MakeMission(
NotationFile*,
ResourceFile*
)
{
Fail("Application::MakeMisson - Should never reach here");
return NULL;
}
//
//#############################################################################
// LoadMissionMessageHandler
//#############################################################################
//
void
Application::LoadMissionMessageHandler(Message *)
{
Check(this);
//
//--------------------------------------------------------------------------
// Load the interest manager
//--------------------------------------------------------------------------
//
#ifdef USE_TIME_ANALYSIS
trace_manager.StartTimingAnalysis();
#endif
Check(interestManager);
interestManager->LoadMission(currentMission);
//
//--------------------------------------------------------------------------
// Make the player
//--------------------------------------------------------------------------
//
Registry* registry = GetRegistry();
Check(registry);
missionPlayer = registry->MakePlayer(currentMission);
Register_Object(missionPlayer);
//
//--------------------------------------------------------------------------
// Set application state to loading
//--------------------------------------------------------------------------
//
applicationState.SetState(CreatingMission);
#if defined(LAB_ONLY)
DEBUG_STREAM << "Starting entity creation...\n" << std::flush;
#endif
}
//
//#############################################################################
// MakeAndLinkViewpointEntity
//#############################################################################
//
Entity*
Application::MakeAndLinkViewpointEntity(Entity::MakeMessage* message)
{
Check(this);
Check(message);
//
//--------------------------------------------------------------------------
// Create the viewpoint entity
//--------------------------------------------------------------------------
//
#if DEBUG_LEVEL>0
HostManager *host = GetHostManager();
Check(host);
Verify(message->entityID.GetHostID() == host->GetLocalHostID());
#endif
Verify(viewpointEntity == NULL);
viewpointEntity = MakeViewpointEntity(message);
//
//--------------------------------------------------------------------------
// Post a message to check the status of the load
//--------------------------------------------------------------------------
//
CheckLoadMessage check_load_message;
Post(DefaultEventPriority, this, &check_load_message);
//
//--------------------------------------------------------------------------
// Load audio renderer
//--------------------------------------------------------------------------
//
if (audioRenderer != NULL)
{
Check(audioRenderer);
audioRenderer->LinkToEntity(viewpointEntity);
audioRenderer->LoadMission(GetCurrentMission());
audioRenderer->SetRendererStatusToRunning();
}
//
//--------------------------------------------------------------------------
// Load video renderer
//--------------------------------------------------------------------------
//
if (videoRenderer != NULL)
{
Check(videoRenderer);
videoRenderer->LinkToEntity(viewpointEntity);
videoRenderer->LoadMission(GetCurrentMission());
videoRenderer->SetRendererStatusToRunning();
}
//
//--------------------------------------------------------------------------
// Load gauge renderer
//--------------------------------------------------------------------------
//
if (gaugeRenderer != NULL)
{
Check(gaugeRenderer);
gaugeRenderer->LinkToEntity(viewpointEntity);
gaugeRenderer->LoadMission(GetCurrentMission());
gaugeRenderer->SetRendererStatusToRunning();
}
return viewpointEntity;
}
//
//#############################################################################
// MakeViewpointEntity
//#############################################################################
//
Entity*
Application::MakeViewpointEntity(Entity::MakeMessage*)
{
Fail("Application::MakeViewpointEntity - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeAudioRenderer
//#############################################################################
//
AudioRenderer*
Application::MakeAudioRenderer()
{
Fail("Application::MakeAudioRenderer - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeVideoRenderer
//#############################################################################
//
VideoRenderer*
Application::MakeVideoRenderer()
{
Fail("Application::MakeVideoRenderer - Should never reach here");
return NULL;
}
//
//#############################################################################
// MakeGaugeRenderer
//#############################################################################
//
GaugeRenderer*
Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
{
Fail("Application::MakeGaugeRenderer - Should never reach here");
return NULL;
}
//
//#############################################################################
// CheckLoadMessageHandler
//#############################################################################
//
void
Application::CheckLoadMessageHandler(
CheckLoadMessage *message
)
{
Check(this);
Check(message);
Verify(message->messageID == CheckLoadMessageID);
//
//--------------------------------------------------------------------------
// If the application is already running then ignore this message
//--------------------------------------------------------------------------
//
switch (applicationState.GetState())
{
case CreatingMission:
case LoadingMission:
case WaitingForLaunch:
if (eventQueue->IsPriorityEmpty(MinEventPriority))
{
Host *console_host;
if (applicationState.GetState() == LoadingMission)
{
ResourceFile *res_file = GetResourceFile();
Check(res_file);
res_file->ReleaseUnlockedResources();
#if defined(LAB_ONLY)
DEBUG_STREAM << "Waiting for translocation!\n" << std::flush;
#endif
#if defined(USE_TIME_ANALYSIS)
DEBUG_STREAM << "Loading time usage:\n" << std::flush;
trace_manager.SnapshotTimingAnalysis(True);
#endif
}
applicationState.SetState(WaitingForLaunch);
Check(GetHostManager());
static bool hasConsolidated = false;
if (!hasConsolidated)
{
//Consolidate level geometry here
VideoRenderer *renderer = Application::GetVideoRenderer();
renderer->ConsolidateStaticObjects();
hasConsolidated = true;
}
console_host = GetHostManager()->GetConsoleHost();
if (
!gConsoleMarshalsLaunch &&
(
(console_host == NULL) ||
(
console_host != NULL &&
console_host->GetConnectStatus() != Host::OnLineConnectionStatus
)
)
)
{
//
// In the absence of the console just post the message to
// run. An IN-PROCESS console (hosted network race) has no
// connection to this pod but still owns the launch - the
// gConsoleMarshalsLaunch flag holds us at WaitingForLaunch
// until every pod in the mesh is staged.
//
RunMissionMessage run_mission_message;
Post(DefaultEventPriority, this, &run_mission_message);
Tell("Sent ready message to ourselves\n");
}
}
//
//-----------------------------------------------------------------------
// Post this message again until the application is running
//-----------------------------------------------------------------------
//
Time post_time;
post_time = Now();
#if DEBUG_LEVEL<3
post_time += 1.0f;
#else
post_time += 5.0;
#endif
Post(DefaultEventPriority, this, message, post_time);
break;
}
}
//
//#############################################################################
// RunMissionMessageHandler
//#############################################################################
//
void
Application::RunMissionMessageHandler(
#if DEBUG_LEVEL>0
RunMissionMessage *message
#else
RunMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == RunMissionMessageID);
//
//--------------------------------------------------------------------------
// If the application is already running then ignore this message
//--------------------------------------------------------------------------
//
switch (GetApplicationState())
{
case RunningMission:
return;
case LaunchingMission:
//
// Start analysis sample
//
#if defined(USE_TRACE_LOG)
{
char *log_size_str = getenv("LOGSIZE");
if (log_size_str)
{
size_t log_size = atoi(log_size_str);
if (log_size > 0)
{
trace_manager.CreateTraceLog(log_size,True);
}
}
}
#endif
#ifdef USE_TIME_ANALYSIS
trace_manager.StartTimingAnalysis();
#endif
Tell("Application::RunMissionMessageHandler - running mission\n");
applicationState.SetState(RunningMission);
gameStarted = Now();
break;
case WaitingForLaunch:
{
Tell("Application::RunMissionMessageHandler - Translocation\n");
applicationState.SetState(LaunchingMission);
Player *player = GetMissionPlayer();
Check(player);
Player::MissionStartingMessage
launch(
Player::MissionStartingMessageID,
sizeof(Player::MissionStartingMessage)
);
player->Dispatch(&launch);
break;
}
default:
Fail("Application::RunMissionMessageHandler - Not ready to run!\n");
break;
}
}
//
//#############################################################################
// SuspendMissionMessageHandler
//#############################################################################
//
void
Application::SuspendMissionMessageHandler(
#if DEBUG_LEVEL>0
SuspendMissionMessage *message
#else
SuspendMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == SuspendMissionMessageID);
//
//--------------------------------------------------------------------------
// The application should be either running or already suspended
//--------------------------------------------------------------------------
//
switch (GetApplicationState())
{
case RunningMission:
{
Player::MissionEndingMessage
player_message(
Player::MissionEndingMessageID,
sizeof(Player::MissionEndingMessage)
);
Player *player;
Tell("Application::SuspendMissionMessageHandler - Suspending\n");
applicationState.SetState(SuspendingMission);
player = GetMissionPlayer();
Check(player);
player->Dispatch(&player_message);
}
break;
case SuspendingMission:
//
// Already suspended
//
break;
default:
//
// Any other state is an error
//
Fail("Application::SuspendMissionMessageHandler - Illegal state");
break;
}
}
//
//#############################################################################
// ResumeMissionMessageHandler
//#############################################################################
//
void
Application::ResumeMissionMessageHandler(
#if DEBUG_LEVEL>0
ResumeMissionMessage *message
#else
ResumeMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == ResumeMissionMessageID);
//
//--------------------------------------------------------------------------
// The application should be in the suspended state or resuming
//--------------------------------------------------------------------------
//
switch (GetApplicationState())
{
case SuspendingMission:
{
Player::MissionStartingMessage
launch(
Player::MissionStartingMessageID,
sizeof(Player::MissionStartingMessage)
);
Player *player;
Tell("Application::ResumeMissionMessageHandler - Resuming mission\n");
applicationState.SetState(ResumingMission);
player = GetMissionPlayer();
Check(player);
player->Dispatch(&launch);
}
break;
case ResumingMission:
Tell("Application::ResumeMissionMessageHandler - Running mission\n");
applicationState.SetState(RunningMission);
gameStarted = Now();
break;
default:
Fail("Application::ResumeMissionMessageHandler - Illegal state\n");
break;
}
}
//
//#############################################################################
// StopMissionMessageHandler
//#############################################################################
//
void
Application::StopMissionMessageHandler(
#if DEBUG_LEVEL>0
StopMissionMessage *message
#else
StopMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == StopMissionMessageID);
//
//--------------------------------------------------------------------------
// If the application is already stopping then ignore the message
//--------------------------------------------------------------------------
//
switch (GetApplicationState())
{
case StoppingMission:
return;
case EndingMission:
case AbortingMission:
Stop();
break;
default:
{
applicationState.SetState(EndingMission);
Player *player = GetMissionPlayer();
if (player)
{
networkManager->Mode(NetworkManager::ReliableMode);
Check(player);
Player::MissionEndingMessage
launch(
Player::MissionEndingMessageID,
sizeof(Player::MissionEndingMessage)
);
player->Dispatch(&launch);
}
else
{
Stop();
}
break;
}
}
}
//
//#############################################################################
// StopMissionMessageHandler
//#############################################################################
//
void
Application::AbortMissionMessageHandler(
#if DEBUG_LEVEL>0
AbortMissionMessage *message
#else
AbortMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == AbortMissionMessageID);
//
//--------------------------------------------------------------------------
// If the application is already stopping then ignore the message
//--------------------------------------------------------------------------
//
switch (GetApplicationState())
{
case StoppingMission:
return;
case EndingMission:
case AbortingMission:
Stop();
break;
default:
{
applicationState.SetState(AbortingMission);
Player *player = GetMissionPlayer();
if (player)
{
Check(player);
Player::MissionEndingMessage
launch(
Player::MissionEndingMessageID,
sizeof(Player::MissionEndingMessage)
);
player->Dispatch(&launch);
}
else
{
Stop();
}
break;
}
}
}
//
//#############################################################################
// KeyCommandMessageHandler
//#############################################################################
//
void
Application::KeyCommandMessageHandler(
ReceiverDataMessageOf<ControlsKey> *message
)
{
Check(this);
Check(message);
switch (message->dataContents)
{
case '&':
if (GetApplicationState() != StoppingMission)
{
Exit_Code = 1;
Stop();
DEBUG_STREAM << "Mission stopped by keystroke!\n" << std::flush;
if (GetApplicationState() == WaitingForEgg)
{
applicationState.SetState(EndingMission);
}
}
break;
#if defined(USE_TRACE_LOG)
case ' ':
trace_manager.MarkTraceLog();
break;
case '/':
trace_manager.ResumeTraceLogging();
break;
case '\\':
trace_manager.SuspendTraceLogging();
break;
#endif
}
}
//~~~~~~~~~~~~~~~~~~~~~~ Application__CheckLoadMessage ~~~~~~~~~~~~~~~~~~~~~~~~
Application__CheckLoadMessage::Application__CheckLoadMessage():
NetworkClient::Message(
Application::CheckLoadMessageID,
sizeof(Application__CheckLoadMessage)
)
{
}
#ifdef TEST_CLASS
# include "app.tcp"
#endif