Playback was laying out a pod's cockpit over a camera's recording - five
instrument panes and the map back in the middle - and it already knew
better. StartConnecting works the answer out from the local host's type and
sets it, but StartConnecting runs after the mission is created and the
cockpit is built before that. The log said so in order: the egg read at
line 9, SVGA16 fitting the cockpit at line 20, and the Live Cam line
arriving at 25. The answer was right and five lines late.
The egg carries it, so read it where the egg is already open: the first
entry in [pilots] is the station that owned the race, and its own section
gives the hostType. That is early enough, and StartConnecting still
confirms the same fact from the host table afterwards.
SVGA16: secondary displays ... map 100%
SVGA16: map on the bottom left (L4MAPPOS)
where it had been radar, centred, on L4RADARPOS.
That is the loop closed: a Live Cam records a race, and the recording
replays with the camera's own cockpit over it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
991 lines
28 KiB
C++
991 lines
28 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4splr.h"
|
|
#include "..\munga\mission.h"
|
|
#include "..\munga\controls.h"
|
|
#include "..\munga\appmsg.h"
|
|
|
|
//#############################################################################
|
|
//####################### SpoolingInterestManager #########################
|
|
//#############################################################################
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
SpoolingInterestManager::SpoolingInterestManager()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
SpoolingInterestManager::~SpoolingInterestManager()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolingInterestManager::LoadInterestArenas(Mission *)
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolingInterestManager::LoadMission(Mission *)
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolingInterestManager::ReceiveNetworkPacket(
|
|
NetworkPacket *packet,
|
|
Receiver::Message *
|
|
)
|
|
{
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// All network messages coming to the interest manager will be spooled
|
|
//--------------------------------------------------------------------
|
|
//
|
|
packet->timeStamp = Now(); // HACK - spoof the clock stuff
|
|
Check(l4_spooling_application);
|
|
SpoolFile *spool = l4_spooling_application->GetSpoolFile();
|
|
Check(spool);
|
|
spool->SpoolPacket(packet);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolingInterestManager::Shutdown()
|
|
{
|
|
Check(this);
|
|
}
|
|
|
|
//#############################################################################
|
|
//###################### L4SpoolingNetworkManager #########################
|
|
//#############################################################################
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4SpoolingNetworkManager::L4SpoolingNetworkManager()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingNetworkManager::StartConnecting(Mission *mission)
|
|
{
|
|
//
|
|
//--------------------------------------------------------
|
|
// Let the L4 network manager start the connection process
|
|
//--------------------------------------------------------
|
|
//
|
|
L4NetworkManager::StartConnecting(mission);
|
|
|
|
//
|
|
//---------------------------------
|
|
// Local the spool file to write to
|
|
//---------------------------------
|
|
//
|
|
Check(l4_spooling_application);
|
|
SpoolFile *spool = l4_spooling_application->GetSpoolFile();
|
|
Check(spool);
|
|
|
|
*(ApplicationID*)spool->GetPointer() = application->GetApplicationID();
|
|
spool->AdvancePointer(sizeof(ApplicationID));
|
|
|
|
ResourceFile *res_file = application->GetResourceFile();
|
|
Check(res_file);
|
|
int major_version = res_file->versionArray[1];
|
|
*(int*)spool->GetPointer() = major_version;
|
|
spool->AdvancePointer(sizeof(major_version));
|
|
|
|
//
|
|
//-----------------------------------------------------------------
|
|
// Now, write out the host IDs. Follow each host ID with a logical
|
|
// indicating whether it is local or remote
|
|
//-----------------------------------------------------------------
|
|
//
|
|
HostManager *host_mgr = application->GetHostManager();
|
|
Check(host_mgr);
|
|
Mission::HostIterator mission_host_iterator(mission);
|
|
MissionHostData *mission_host_data;
|
|
|
|
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
CString host_name(mission_host_data->GetAddressString());
|
|
SOCKADDR_IN net_address;
|
|
|
|
ResolveAddress(host_name, &net_address);
|
|
Host *host = host_mgr->FindHost(net_address);
|
|
|
|
//
|
|
// FindHost answers NULL for a host named in the egg that is not
|
|
// actually connected, and this went straight on to call
|
|
// host->GetHostID(). In an arcade every station in the egg is on
|
|
// the wire, so the case could not arise; anywhere else it is the
|
|
// ordinary state of affairs, and it crashed the spooling
|
|
// application before the mission could even start.
|
|
//
|
|
// The table below is read back one pair per egg host, in egg
|
|
// order, so a missing host cannot simply be skipped - that would
|
|
// shift every entry after it. Write the pair, say what happened,
|
|
// and carry on.
|
|
//
|
|
if (host == NULL)
|
|
{
|
|
DEBUG_STREAM << "Spool: host '" << host_name
|
|
<< "' is in the egg but not connected - recording it as"
|
|
<< " remote with no ID. Packets from it will not map back.\n"
|
|
<< std::flush;
|
|
|
|
*(Logical*)spool->GetPointer() = True;
|
|
spool->AdvancePointer(sizeof(Logical));
|
|
|
|
*(HostID*)spool->GetPointer() = (HostID) 0;
|
|
spool->AdvancePointer(sizeof(HostID));
|
|
continue;
|
|
}
|
|
|
|
*(Logical*)spool->GetPointer() = (host != host_mgr->GetLocalHost());
|
|
spool->AdvancePointer(sizeof(Logical));
|
|
|
|
*(HostID*)spool->GetPointer() = host->GetHostID();
|
|
spool->AdvancePointer(sizeof(HostID));
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4SpoolingNetworkManager::~L4SpoolingNetworkManager()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingNetworkManager::ReceiveNetworkPacket(
|
|
NetworkPacket *packet,
|
|
Receiver::Message *message
|
|
)
|
|
{
|
|
//
|
|
//------------------------------------------------------
|
|
// Any egg messages will be spooled before being sent on
|
|
//------------------------------------------------------
|
|
//
|
|
if (message->messageID == ReceiveEggFileMessageID)
|
|
{
|
|
packet->timeStamp = Now(); // HACK - spoof the clock stuff
|
|
Check(l4_spooling_application);
|
|
SpoolFile *spool = l4_spooling_application->GetSpoolFile();
|
|
Check(spool);
|
|
spool->SpoolPacket(packet);
|
|
}
|
|
L4NetworkManager::ReceiveNetworkPacket(packet, message);
|
|
}
|
|
|
|
//#############################################################################
|
|
//###################### L4PlaybackNetworkManager #########################
|
|
//#############################################################################
|
|
|
|
//
|
|
// Whether the spool's header has been taken off the front of the stream, so
|
|
// SpoolerTask::Execute knows when the cursor is standing on a packet rather
|
|
// than on the header. Cleared when a playback manager is built, since the
|
|
// single-binary loop can play a second spool in the same process.
|
|
//
|
|
namespace
|
|
{
|
|
Logical gSpoolHeaderConsumed = False;
|
|
}
|
|
|
|
Logical
|
|
SpoolHeaderConsumed()
|
|
{
|
|
return gSpoolHeaderConsumed;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4PlaybackNetworkManager::L4PlaybackNetworkManager():
|
|
NetworkManager(L4PlaybackNetworkManager::DefaultData)
|
|
{
|
|
gSpoolHeaderConsumed = False;
|
|
|
|
//
|
|
// Give this application its mission.
|
|
//
|
|
// Nothing else will. A spool is a record of what MOVED, not of the
|
|
// world it moved through - no track, no models, no drop zones - and
|
|
// playback has neither a wire to be sent an egg over nor a console to
|
|
// send one. L4NetworkManager does exactly this for single user mode,
|
|
// but that is the pod's network manager; this one descends from
|
|
// NetworkManager and inherited none of it, so a playback build came up
|
|
// with a cockpit, no world behind it, and nothing in the log to say
|
|
// why. A black screen is what that looks like.
|
|
//
|
|
const char *egg_name =
|
|
((L4Application *) application)->GetEggNotationFileName();
|
|
|
|
//
|
|
// Fall back to the egg saved with the recording.
|
|
//
|
|
// Every spool is written with its egg beside it under the same stem,
|
|
// and last.egg alongside last.spl, precisely so that a recording is one
|
|
// self-contained thing. Having to name the egg by hand invites naming
|
|
// the WRONG one - frontend.egg is rewritten by the next race set up on
|
|
// the machine, and a spool played against a different track would load
|
|
// happily and show nonsense.
|
|
//
|
|
static char found_egg[MAX_PATH];
|
|
|
|
if (egg_name == NULL || strlen(egg_name) == 0)
|
|
{
|
|
CString spool_name = ((L4Application *) application)->GetSpoolFileName();
|
|
const char *spool_text =
|
|
(!spool_name) ? "last.spl" : (const char *) spool_name;
|
|
|
|
strncpy(found_egg, spool_text, sizeof(found_egg) - 1);
|
|
found_egg[sizeof(found_egg) - 1] = '\0';
|
|
|
|
size_t length = strlen(found_egg);
|
|
if (length > 4)
|
|
{
|
|
strcpy(found_egg + length - 4, ".egg");
|
|
|
|
FILE *probe = fopen(found_egg, "r");
|
|
if (probe != NULL)
|
|
{
|
|
fclose(probe);
|
|
egg_name = found_egg;
|
|
DEBUG_STREAM << "Playback: using the egg saved with the"
|
|
<< " recording, '" << found_egg << "'\n" << std::flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (egg_name == NULL || strlen(egg_name) == 0)
|
|
{
|
|
DEBUG_STREAM << "Playback: no egg. A spool records the race but not the"
|
|
<< " track it was run on, and no egg was found beside the spool -"
|
|
<< " name one with -egg.\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
DEBUG_STREAM << "Playback: loading world from egg '" << egg_name
|
|
<< "'\n" << std::flush;
|
|
|
|
networkEggNotationFile = new NotationFile(egg_name);
|
|
Register_Object(networkEggNotationFile);
|
|
|
|
//
|
|
// Decide now whether this is a camera station, not later.
|
|
//
|
|
// StartConnecting already works this out from the local host's type and
|
|
// sets it - but StartConnecting runs after the mission is created, and
|
|
// the cockpit is built before that. The log says so plainly: the egg is
|
|
// read here, SVGA16 fits the cockpit eleven lines later, and the Live
|
|
// Cam line arrives five lines after THAT. So a camera's recording came
|
|
// back with a pod's cockpit over it - five instrument panes and the map
|
|
// in the middle - because the answer arrived after the panes had been
|
|
// made.
|
|
//
|
|
// The egg has it: the first entry in [pilots] is the station that
|
|
// owned the race, and its own section carries the hostType. Reading it
|
|
// here is early enough, and it is the same fact StartConnecting will
|
|
// confirm from the host table afterwards.
|
|
//
|
|
const char *owner_address;
|
|
|
|
if (networkEggNotationFile->GetEntry("pilots", "pilot", &owner_address))
|
|
{
|
|
int owner_host_type = 0;
|
|
|
|
if (networkEggNotationFile->GetEntry(
|
|
owner_address, "hostType", &owner_host_type))
|
|
{
|
|
Logical owner_is_camera =
|
|
(owner_host_type == (int) CameraShipHostType);
|
|
|
|
Application::SetCameraStation(owner_is_camera);
|
|
DEBUG_STREAM << "Playback: the owning station '" << owner_address
|
|
<< "' recorded as hostType " << owner_host_type
|
|
<< (owner_is_camera
|
|
? " - a Live Cam, so no instrument panes and the map"
|
|
" landscape in the corner"
|
|
: " - a pod, so the full cockpit")
|
|
<< "\n" << std::flush;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The handler is NetworkManager's, and it ends in CreateMission, which
|
|
// is what calls StartConnecting below to read the host table back out
|
|
// of the spool.
|
|
//
|
|
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
|
|
application->Post(DefaultEventPriority, this, &egg_message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4PlaybackNetworkManager::StartConnecting(Mission *mission)
|
|
{
|
|
//
|
|
// Create the host iterator for the mission egg
|
|
//
|
|
Mission::HostIterator mission_host_iterator(mission);
|
|
MissionHostData *mission_host_data;
|
|
Check(application);
|
|
SpoolFile *spool = application->GetSpoolFile();
|
|
Check(spool);
|
|
|
|
ApplicationID id = *(ApplicationID*)spool->GetPointer();
|
|
spool->AdvancePointer(sizeof(id));
|
|
int major_rev = *(int*)spool->GetPointer();
|
|
spool->AdvancePointer(sizeof(major_rev));
|
|
|
|
if (id != application->GetApplicationID())
|
|
{
|
|
DEBUG_STREAM << "\n\nError - Not a spool file for this application!\n" << std::flush;
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
|
|
ResourceFile *resource_file = application->GetResourceFile();
|
|
Check(resource_file);
|
|
|
|
if (major_rev != resource_file->versionArray[1])
|
|
{
|
|
DEBUG_STREAM << "\n\nError - Spool file major data version should be "
|
|
<< (int)resource_file->versionArray[1] << ", not " << major_rev
|
|
<< "!\n" << std::flush;
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
|
|
HostManager *host_mgr = application->GetHostManager();
|
|
Check(host_mgr);
|
|
|
|
//
|
|
// Iterate through all the host addresses in the egg and create all the host
|
|
// structures, note that there MUST be an entry in the egg for us.
|
|
//
|
|
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(mission_host_data);
|
|
CString host_name(mission_host_data->GetAddressString());
|
|
|
|
Logical remote = *(Logical*)spool->GetPointer();
|
|
spool->AdvancePointer(sizeof(Logical));
|
|
HostID host_id = *(HostID*)spool->GetPointer();
|
|
spool->AdvancePointer(sizeof(HostID));
|
|
|
|
Host *my_host =
|
|
new L4Host(
|
|
host_id,
|
|
mission_host_data->GetHostType(),
|
|
NULL,
|
|
INVALID_SOCKET,
|
|
host_name
|
|
);
|
|
Register_Object(my_host);
|
|
if (remote)
|
|
{
|
|
host_mgr->AdoptRemoteHost(my_host);
|
|
}
|
|
else
|
|
{
|
|
host_mgr->AdoptLocalHost(my_host);
|
|
|
|
//
|
|
// Lay the cockpit out as whatever this station WAS.
|
|
//
|
|
// Application::SetCameraStation is normally the front end's
|
|
// answer to a question asked on the setup screen, and playback
|
|
// never sees the setup screen - so a recording made from a Live
|
|
// Cam replayed with five instrument panes hung over the view
|
|
// and the map back in the middle, which is a pod's cockpit, not
|
|
// a camera's. The egg knows: it is the same egg the race ran
|
|
// on, and it says what this host was.
|
|
//
|
|
Application::SetCameraStation(
|
|
mission_host_data->GetHostType() == CameraShipHostType
|
|
);
|
|
if (mission_host_data->GetHostType() == CameraShipHostType)
|
|
{
|
|
DEBUG_STREAM << "Playback: this station recorded as a Live Cam"
|
|
<< " - no instrument panes, map landscape\n" << std::flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Did the header end where the packets begin?
|
|
//
|
|
// The table above holds one pair per host named in the EGG, and the
|
|
// reader trusts the egg to have as many hosts as the spool was written
|
|
// with. Hand it a different egg and the count differs, the read pointer
|
|
// stops short of - or past - the first packet, and every packet after
|
|
// that is parsed from the middle of something else. The first symptom is
|
|
// a nonsense message length, and the second is a corrupted heap: exit
|
|
// 0xC0000374, no message, nothing in the log.
|
|
//
|
|
// A packet here should start with a plausible length. If it does not,
|
|
// the egg does not belong to this spool, and saying so is worth more
|
|
// than whatever the heap does next.
|
|
//
|
|
{
|
|
NetworkPacket *first = (NetworkPacket*) spool->GetPointer();
|
|
int first_length = (int) first->messageData.messageLength;
|
|
|
|
if (first_length < (int) sizeof(Receiver::Message)
|
|
|| first_length > 65536
|
|
|| first_length > (int) spool->GetBytesRemaining())
|
|
{
|
|
DEBUG_STREAM << "\n\nError - this egg does not belong to this spool."
|
|
<< " The host table ran to the wrong length and the first packet"
|
|
<< " reads as " << first_length << " bytes, which cannot be"
|
|
<< " right. Play it back with the .egg that was saved beside"
|
|
<< " it.\n" << std::flush;
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The cursor is now standing on the first packet, so the spooler task
|
|
// may start reading. Set only after the sanity check above, so a spool
|
|
// whose header did not add up is never played at all.
|
|
//
|
|
gSpoolHeaderConsumed = True;
|
|
DEBUG_STREAM << "Playback: header consumed, "
|
|
<< (int) spool->GetBytesRemaining() << " bytes of packets to play\n"
|
|
<< std::flush;
|
|
|
|
//
|
|
// Now, just send the load message
|
|
//
|
|
Check(application);
|
|
Application::Message
|
|
load_message(
|
|
Application::LoadMissionMessageID,
|
|
sizeof(Application::Message)
|
|
);
|
|
application->Post(DefaultEventPriority, application, &load_message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4PlaybackNetworkManager::Shutdown()
|
|
{
|
|
Check(this);
|
|
NetworkManager::Shutdown();
|
|
|
|
Check(application);
|
|
HostManager *host_mgr = application->GetHostManager();
|
|
Check(host_mgr);
|
|
HostManager::RemoteHostIterator remote_hosts(host_mgr);
|
|
Host *my_host;
|
|
while ((my_host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
Check(my_host);
|
|
host_mgr->OrphanRemoteHost(my_host);
|
|
Unregister_Object(my_host);
|
|
delete my_host;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4PlaybackNetworkManager::CheckBuffers(NetworkPacket*)
|
|
{
|
|
//
|
|
// The buffers are always empty
|
|
//
|
|
return False;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4PlaybackNetworkManager::~L4PlaybackNetworkManager()
|
|
{
|
|
}
|
|
|
|
//#############################################################################
|
|
//###################### L4SpoolingApplication ##########################
|
|
//#############################################################################
|
|
|
|
L4SpoolingApplication*&
|
|
l4_spooling_application = (L4SpoolingApplication*&)application;
|
|
|
|
const Receiver::HandlerEntry
|
|
L4SpoolingApplication::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(L4SpoolingApplication, RunMission),
|
|
MESSAGE_ENTRY(L4SpoolingApplication, StopMission),
|
|
MESSAGE_ENTRY(L4SpoolingApplication, AbortMission),
|
|
MESSAGE_ENTRY(L4SpoolingApplication, LoadMission)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& L4SpoolingApplication::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(L4SpoolingApplication::MessageHandlerEntries), L4SpoolingApplication::MessageHandlerEntries, L4Application::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Virtual Data support
|
|
//
|
|
Derivation* L4SpoolingApplication::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(L4Application::GetClassDerivations(), "L4SpoolingApplication");
|
|
return &classDerivations;
|
|
}
|
|
|
|
L4SpoolingApplication::SharedData
|
|
L4SpoolingApplication::DefaultData(
|
|
L4SpoolingApplication::GetClassDerivations(),
|
|
L4SpoolingApplication::GetMessageHandlers()
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::LoadMissionMessageHandler(Message *)
|
|
{
|
|
applicationState.SetState(WaitingForLaunch);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::RunMissionMessageHandler(RunMissionMessage *)
|
|
{
|
|
Check(this);
|
|
|
|
applicationState.SetState(RunningMission);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void L4SpoolingApplication::StopMissionMessageHandler(StopMissionMessage *message)
|
|
{
|
|
if (!missionSpooled)
|
|
{
|
|
missionSpooled = True;
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Post this message again until the application is running
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
Time post_time = Now();
|
|
post_time += 3.0f;
|
|
|
|
Post(DefaultEventPriority, this, message, post_time);
|
|
}
|
|
else
|
|
{
|
|
MissionReviewApplicationManager *app_mgr = GetApplicationManager();
|
|
Check(app_mgr);
|
|
SpoolFile *spool = GetSpoolFile();
|
|
Check(spool);
|
|
app_mgr->StoreSpoolFile(spool);
|
|
spoolFile = NULL;
|
|
|
|
L4Application::StopMissionMessageHandler(message);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::AbortMissionMessageHandler(
|
|
AbortMissionMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(spoolFile);
|
|
spoolFile->Rewind();
|
|
|
|
L4Application::AbortMissionMessageHandler(message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::ReceiveNetworkPacket(
|
|
NetworkPacket *packet,
|
|
Receiver::Message *message
|
|
)
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//------------------------------------------------------
|
|
// Any egg messages will be spooled before being sent on
|
|
//------------------------------------------------------
|
|
//
|
|
switch (message->messageID)
|
|
{
|
|
case StopMissionMessageID:
|
|
if (missionSpooled)
|
|
{
|
|
packet->timeStamp = Now(); // HACK - spoof the clock stuff
|
|
SpoolFile *spool = GetSpoolFile();
|
|
Check(spool);
|
|
spool->SpoolPacket(packet);
|
|
break;
|
|
}
|
|
|
|
case RunMissionMessageID:
|
|
packet->timeStamp = Now(); // HACK - spoof the clock stuff
|
|
SpoolFile *spool = GetSpoolFile();
|
|
Check(spool);
|
|
spool->SpoolPacket(packet);
|
|
break;
|
|
}
|
|
L4Application::ReceiveNetworkPacket(packet, message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4SpoolingApplication::L4SpoolingApplication(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
ApplicationID application_ID
|
|
):
|
|
L4Application(
|
|
hInstance,
|
|
hWnd,
|
|
resource_file,
|
|
application_ID,
|
|
L4ApplicationClassID,
|
|
DefaultData
|
|
)
|
|
{
|
|
missionSpooled = False;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Registry*
|
|
L4SpoolingApplication::MakeRegistry()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
InterestManager*
|
|
L4SpoolingApplication::MakeInterestManager()
|
|
{
|
|
return new SpoolingInterestManager;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
NetworkManager*
|
|
L4SpoolingApplication::MakeNetworkManager()
|
|
{
|
|
return new L4SpoolingNetworkManager;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::Initialize()
|
|
{
|
|
Check(this);
|
|
Application::Initialize();
|
|
|
|
//
|
|
// Create the console host and socket
|
|
//
|
|
L4NetworkManager *net_mgr = GetNetworkManager();
|
|
Check(net_mgr);
|
|
net_mgr->CreateConsoleHost();
|
|
|
|
//
|
|
// Allocate the spool file
|
|
//
|
|
MissionReviewApplicationManager *app_mgr = GetApplicationManager();
|
|
Check(app_mgr);
|
|
spoolFile = app_mgr->GetEmptySpoolFile();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4SpoolingApplication::LoadBackgroundTasks()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
VideoRenderer*
|
|
L4SpoolingApplication::MakeVideoRenderer()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
AudioRenderer*
|
|
L4SpoolingApplication::MakeAudioRenderer()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
GaugeRenderer*
|
|
L4SpoolingApplication::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Mission*
|
|
L4SpoolingApplication::MakeMission(
|
|
NotationFile *notation_file,
|
|
ResourceFile *resources
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(notation_file);
|
|
Check(resources);
|
|
|
|
//////////// return new L4Mission(notation_file, resources);
|
|
return new Mission(notation_file, resources);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity*
|
|
L4SpoolingApplication::MakeViewpointEntity(Entity__MakeMessage *)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical L4SpoolingApplication::ExecuteForeground(Time, Scalar)
|
|
{
|
|
SET_FOREGROUND_PROCESSING();
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// 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);
|
|
controlsManager->Execute();
|
|
|
|
CLEAR_FOREGROUND_PROCESSING();
|
|
return executeFrames && !Exit_Code;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void L4SpoolingApplication::ExecuteBackgroundTask()
|
|
{
|
|
Check(this);
|
|
|
|
Check(networkManager);
|
|
if (!networkManager->RoutePacket())
|
|
{
|
|
ProcessOneEvent(DefaultEventPriority);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4SpoolingApplication::Shutdown(int remainingApps)
|
|
{
|
|
Check(this);
|
|
|
|
L4Application::Shutdown(remainingApps);
|
|
return !Exit_Code && !missionSpooled;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4SpoolingApplication::~L4SpoolingApplication()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4SpoolingApplication::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|
|
//#############################################################################
|
|
//########################### SpoolerTask ###############################
|
|
//#############################################################################
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
SpoolerTask::SpoolerTask()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolerTask::DispatchPacket(NetworkPacket *)
|
|
{
|
|
Fail("SpoolerTask::DispatchPacket - shouldn't be here!");
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
long
|
|
SpoolerTask::GetTimeBias()
|
|
{
|
|
Fail("SpoolerTask::GetTimeBias - shouldn't be here!");
|
|
return 0;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
SpoolerTask::Execute()
|
|
{
|
|
Check(this);
|
|
Check(application);
|
|
|
|
SpoolFile* spool = application->GetSpoolFile();
|
|
if (!spool)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Nothing may be read until the header has been taken off the front.
|
|
//
|
|
// A spool opens with the application ID, the resource version and one
|
|
// (remote, hostID) pair per host named in the egg, and the length of
|
|
// that depends on the egg - so it can only be read once the mission
|
|
// exists, which is what L4PlaybackNetworkManager::StartConnecting does.
|
|
// Until then the cursor is sitting on the header, and this function
|
|
// happily parsed it as packet one: the application ID read as a client
|
|
// ID, and ours is 0, which is NetworkManagerClientID exactly. The pair
|
|
// of host IDs after it read as a message length of 1 and a message ID
|
|
// of 3 - and message 3 on the network manager is ReceiveEggFile, which
|
|
// builds a Mission. From a packet. That was the heap corruption.
|
|
//
|
|
// The WaitingForEgg case below is for an arcade spool that CARRIES its
|
|
// egg as network manager packets, which cannot work with this format
|
|
// anyway: reading those packets means passing the header first, and
|
|
// passing the header means already knowing the egg. A recording made
|
|
// by this build keeps its egg beside it instead, which is why there is
|
|
// no such circle to break.
|
|
//
|
|
if (!SpoolHeaderConsumed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// We have a spool file, so interpret it based upon the application state
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
Check(spool);
|
|
NetworkPacket *packet;
|
|
switch (application->GetApplicationState())
|
|
{
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If we are waiting for an egg, post the egg messages to the application
|
|
// from the spool
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
case Application::WaitingForEgg:
|
|
packet = (NetworkPacket*)spool->GetPointer();
|
|
while (packet->clientID == NetworkClient::NetworkManagerClientID)
|
|
{
|
|
spool->NextPacket();
|
|
DispatchPacket(packet);
|
|
packet = (NetworkPacket*)spool->GetPointer();
|
|
}
|
|
break;
|
|
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// If the application is loading, then move off the block of interest
|
|
// messages into the interest manager
|
|
//-------------------------------------------------------------------
|
|
//
|
|
case Application::CreatingMission:
|
|
case Application::LoadingMission:
|
|
packet = (NetworkPacket*)spool->GetPointer();
|
|
while (
|
|
packet->clientID != NetworkClient::ApplicationClientID
|
|
&& spool->GetBytesRemaining() > 0
|
|
)
|
|
{
|
|
spool->NextPacket();
|
|
DispatchPacket(packet);
|
|
packet = (NetworkPacket*)spool->GetPointer();
|
|
}
|
|
break;
|
|
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// These messages need to be doled out based upon the system time, as
|
|
// modified by when the run message was received
|
|
//-------------------------------------------------------------------
|
|
//
|
|
|
|
case Application::LaunchingMission:
|
|
case Application::RunningMission:
|
|
while (spool && spool->GetBytesRemaining() > 0)
|
|
{
|
|
packet = (NetworkPacket*)spool->GetPointer();
|
|
Time when = packet->timeStamp;
|
|
when.ticks += GetTimeBias();
|
|
if (when > Now())
|
|
{
|
|
break;
|
|
}
|
|
spool->NextPacket();
|
|
DispatchPacket(packet);
|
|
spool = application->GetSpoolFile();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
SpoolerTask::~SpoolerTask()
|
|
{
|
|
}
|