Files
RP412/MUNGA/APP.cpp
T
CydandClaude Fable 5 7d485c9672 The wire keeps what it could not send
Cyd asked for an analysis of the networking stack and what would make the
simulation feel better over the internet. The analysis found something
more urgent than latency: the transport has been losing data silently
since the arcade, and nothing in the game could see it happen.

Every send result was discarded - L4NET, the console, all of it. On the
1ms arcade LAN the socket buffer never filled, so it never mattered. Over
the internet it matters twice. A peer stalled in its own 10-30 second
mission load stops reading, its window closes, and our nonblocking send
starts answering would-block, which threw the message away; or worse,
answering a PARTIAL count, and since framing on that stream is recovered
purely from each message's length prefix, the bytes that never followed
sheared it for good. Both are reachable in an ordinary race, because
every race has a load in it.

So sends go through a bounded per-connection queue now. What the wire
will not take is kept, byte-exact, and retried at three flush points -
before the render (the present blocks on vsync, and this frame's state
should be travelling while it does), at the top of the receive pump, and
before a connect sequence. Nothing is ever dropped from the middle: these
are reliable ordered messages carrying entity creation, damage and race
control, so a queue that overflows its 256K declares the connection dead
and lets the disconnect path run rather than quietly desyncing the
stream. RP412NETSENDQ=0 restores the old behaviour and still logs what it
would have lost, which is the honest way to A/B it. On Steam the same
queue finally surfaces k_EResultLimitExceeded, which the old code
collapsed into -1 and discarded - that was backpressure, unlogged.

The receive side gained the check the release build never had. The length
prefix is untrusted input; Verify() compiles away in release, so a
corrupt one went to memmove as a negative, or copied 4096 bytes of
assembled packet into a 1600-byte stack buffer, or named a size the pad
could never complete and wedged the connection forever. It is now
validated against the same bounds the sender works to, and a stream that
fails them is dropped like any other lost peer.

And a fry that never ends: drop zones are map entities dealt round-robin
at load, ownership transfer is not implemented, so a leaver's pads stay
in the DropZones group. The respawn request dispatched to one goes to a
host that is gone - dropped at the send, the 'no host N in the table'
path - and the two-second retry re-dispatches to the same dead owner
forever. The pad scan now skips zones whose owner has left, and
re-validates one assigned earlier before reusing it.

The rest is measurement, because the symptoms this work exists to chase
are all reported in prose and none of them are in any log. Sixteen logs
from the six-player night contain zero player-facing latency lines. A
race now ends with a NetLog summary: per remote pod, how many updates
arrived and how evenly (median and p95 out of a log2 histogram), the
widest gap, how many gaps were long enough to mean a quiet sender versus
short enough to mean OUR loop stalled, how often its motion snapped
instead of blending, and how far arriving updates moved it. Per peer,
whether the clock alignment ever had to step mid-race - which is the
input for deciding if it needs slewing, rather than guessing. The mission
t0 tick goes in the log too, alongside the console's per-pod RunMission
send ticks, because nothing has ever measured how far apart the machines
actually start; the clockwork doors inherit that skew directly.

RP412NETSTATS adds the transport's own view - per connection: messages,
bytes, wire writes, partials, refusals, how much sat queued - and on
Steam the first read this codebase has ever taken of GetConnectionRealTime
Status. Ping, quality, pending and unacked bytes, and one route
description per connection at teardown. The API was vendored and never
called; there was no RTT number anywhere in the game.

Finally, rpl4opt -spoolstats reads any recording offline. The data was
already in every spool ever made and nothing read it that way: the
recorder restamps each packet with local arrival time while the update
records inside keep the sender's sim-grid stamp, so the difference is
clock offset plus one-way delay, and the same running-minimum estimator
the game runs live separates them. It prints delay above the per-host
minimum, and decomposes each entity's gaps into sender pacing versus
delivery jitter - which no live counter can do. It lives in the game exe
rather than RPL4TOOL because the tool is deliberately not /Zp1 and would
misread every struct in the file.

Verified on the two-pod loopback harness: mesh up, egg fed, 60s raced,
stopped on command, scores collected, and both summaries reading exactly
what a pair of PARKED pods should read - heartbeat cadence, one snap per
heartbeat, sub-quarter-metre corrections, no clock steps. The t0 ticks
and the netclock offsets agree with each other to the two seconds the
pods launched apart.

The latency tier is deliberately NOT here. TCP_NODELAY, the Steam
NoNagle flag, per-frame coalescing and the pre-sim receive drain are all
scoped and all wait on this build's numbers, because the point of
shipping measurement first is to find out whether the thing we would fix
is the thing that hurts. Nagle is still on. Interest management is still
inert. The wire format is untouched, so this build and the last one still
race each other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:59:25 -05:00

2167 lines
56 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;
Logical Application::cameraStation = False;
Logical Application::recordMission = False;
//
// RP412CAMLOG - see app.h. Cached: the waiting trace asks once a second
// for as long as a station sits unlaunched.
//
Logical
RPCameraLog()
{
static int enabled = -1;
if (enabled < 0)
{
const char *setting = getenv("RP412CAMLOG");
enabled = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
}
return enabled ? True : False;
}
//
// RP412NETLOG - the race-end network summary, on unless =0. The latch
// keeps the summary to exactly one print per mission: RunMission arms
// it, the first stop or abort fires it (see NetLogReportRace, further
// down beside the stop handlers).
//
static Logical gNetRaceReported = True;
static Logical
NetLogEnabled()
{
static int enabled = -1;
if (enabled < 0)
{
const char *setting = getenv("RP412NETLOG");
enabled = (setting != NULL && atoi(setting) == 0) ? 0 : 1;
}
return enabled ? True : 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.
//--------------------------------------------------------------------------
//
//
// Flush queued sends before the render: the present can block on
// vsync, and this frame's state updates should be on the wire while
// that happens, not behind it.
//
Check(networkManager);
networkManager->FlushSends();
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);
if (RPCameraLog())
{
DEBUG_STREAM << "CamLog: player data set - loading interest arenas\n" << std::flush;
}
interest_mgr->LoadInterestArenas(currentMission);
if (RPCameraLog())
{
DEBUG_STREAM << "CamLog: interest arenas loaded - mission created\n" << std::flush;
}
}
//
//#############################################################################
// 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);
if (RPCameraLog())
{
DEBUG_STREAM << "CamLog: LoadMission handler - loading interest manager\n" << std::flush;
}
interestManager->LoadMission(currentMission);
if (RPCameraLog())
{
DEBUG_STREAM << "CamLog: interest manager loaded - about to make the player\n"
<< std::flush;
}
//
//--------------------------------------------------------------------------
// 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 (RPCameraLog())
{
DEBUG_STREAM << "CamLog: CheckLoad state=" << (int) applicationState.GetState()
<< " minPriorityQueueEmpty="
<< (int) (eventQueue->IsPriorityEmpty(MinEventPriority) ? 1 : 0)
<< "\n" << std::flush;
}
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");
if (RPCameraLog())
{
DEBUG_STREAM << "CamLog: no console - posted RunMission to ourselves\n"
<< std::flush;
}
}
}
//
//-----------------------------------------------------------------------
// 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();
//
// Mission t0, in this machine's ticks (RP412NETLOG). Every
// machine stamps its own t0 when this message is PROCESSED, so
// the mission clocks - and everything phase-derived from them,
// the clockwork doors included - skew by the one-way spread of
// the console's RunMission sends plus frame quantization. This
// line plus the console's per-pod send ticks plus the netclock
// offsets are what let the skew be computed offline for the
// first time (plan item M4).
//
DEBUG_STREAM << "NetLog: mission t0 at tick " << gameStarted.ticks
<< "\n" << std::flush;
gNetRaceReported = False; // arm the race-end summary
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;
}
}
//
//#############################################################################
// The race-end network summary (RP412NETLOG, on unless =0).
//
// Playtest logs used to carry ZERO player-symptom lines - every reported
// tick, warp and stall lived in commit prose while sixteen logs said
// nothing. These few lines are the fix: per-replicant race totals
// (accumulated ungated in Entity::netRaceStats) and the per-peer clock
// lines, printed once when the first stop or abort lands, before
// teardown. The latch (gNetRaceReported, defined up beside RPCameraLog)
// is armed by RunMission, so menu traffic never prints and a second
// stop cannot print twice.
//#############################################################################
//
// A percentile out of the log2-millisecond histogram, reported as the
// bucket's lower bound: "~32 ms" means the value fell in [32, 64).
//
static unsigned long
NetLogPercentileMs(
const unsigned long *buckets,
unsigned long total,
unsigned long percent)
{
if (total == 0)
{
return 0;
}
unsigned long target = (total * percent + 99) / 100;
unsigned long seen = 0;
for (int b = 0; b < 16; ++b)
{
seen += buckets[b];
if (seen >= target)
{
return (b == 0) ? 0 : (1UL << (b - 1));
}
}
return 1UL << 14;
}
static void
NetLogReportRace(Application *the_application)
{
if (gNetRaceReported || !NetLogEnabled())
{
return;
}
gNetRaceReported = True;
DEBUG_STREAM << "NetLog: race summary at tick " << Now().ticks
<< ", elapsed " << the_application->GetMissionElapsed()
<< " s\n" << std::flush;
HostManager::DynamicReplicantEntityIterator
replicants(the_application->GetHostManager());
Entity *entity;
while ((entity = replicants.ReadAndNext()) != NULL)
{
Check(entity);
Entity::NetRaceStats *stats = &entity->netRaceStats;
if (stats->updateCount == 0)
{
continue;
}
DEBUG_STREAM << "NetLog: pod " << entity->GetEntityID()
<< " host " << entity->GetOwnerID()
<< ": " << stats->updateCount << " updates"
<< ", interval med/p95 ~"
<< NetLogPercentileMs(stats->gapHistogram, stats->updateCount, 50)
<< "/~"
<< NetLogPercentileMs(stats->gapHistogram, stats->updateCount, 95)
<< " ms, widest " << stats->widestGapSeconds << " s, "
<< stats->longGapCount << " long / "
<< stats->queuedGapCount << " queued, "
<< stats->snapCount << " snaps, corrections "
<< stats->correctionCount;
if (stats->correctionCount > 0)
{
DEBUG_STREAM << " mean "
<< (stats->correctionTotal / (Scalar) stats->correctionCount)
<< " m worst " << stats->correctionWorst << " m";
}
DEBUG_STREAM << ", stale " << stats->staleCount
<< "\n" << std::flush;
}
NetClock_ReportRaceStats();
}
//
//#############################################################################
// StopMissionMessageHandler
//#############################################################################
//
void
Application::StopMissionMessageHandler(
#if DEBUG_LEVEL>0
StopMissionMessage *message
#else
StopMissionMessage *
#endif
)
{
Check(this);
Check(message);
Verify(message->messageID == StopMissionMessageID);
//
// The network race summary goes out on the FIRST stop, while the
// replicants still exist (teardown is downstream of here).
//
NetLogReportRace(this);
//
//--------------------------------------------------------------------------
// 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);
// same first-stop summary as StopMission - an aborted race counts
NetLogReportRace(this);
//
//--------------------------------------------------------------------------
// 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