Two faults, and playback runs. The spooler task was reading before the header came off the stream. The header's length depends on the egg's host count, so it can only be taken off once the mission exists - which is L4PlaybackNetworkManager::StartConnecting - and until then the cursor sits on the header. The task parsed it as packet one: our application ID is 0, which is NetworkManagerClientID exactly, and the two host IDs behind it read as a message length of 1 and a message ID of 3. Message 3 on the network manager is ReceiveEggFile, so it built a Mission from the header and died inside it. The task now waits for SpoolHeaderConsumed, set only after the header has been read AND passed its sanity check, so a spool that does not add up is never played at all. The arcade's WaitingForEgg case, which replays an egg carried in the spool as network manager packets, cannot work with this format in any case: 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, so there is no such circle. And last.egg had three other writers, not one. Every network manager dumps the egg it loaded to that name as a debug aid - networkEggNotationFile->WriteFile - and it writes a notation image, 101,920 bytes of it, straight over the 9,470 byte text egg saved with the recording. Playback then read that as its egg, found no map entry, and Mission::Mission carried on past its own PostQuitMessage with an uninitialised map name to dereference a NULL resource. All three dumps are last-loaded.egg now, and last.egg belongs to the recording alone. Worth noting the copy was never wrong: SPOOLS\<stamp>.egg is byte-identical to the frontend.egg it came from. Only the last.egg convenience copy was being overwritten, which is the sort of thing that looks like a corrupt recording and is not. Where it stands: -pb loads the spool, takes the egg saved beside it, lays the cockpit out as the Live Cam it was recorded from, consumes the header, and dispatches the race's packets to the interest manager with sane lengths, without crashing. Whether the pods MOVE on screen is the next thing to look at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
473 lines
12 KiB
C++
473 lines
12 KiB
C++
#include "munga.h"
|
|
#pragma hdrstop
|
|
|
|
#include "hostmgr.h"
|
|
#include "interest.h"
|
|
#include "icom.h"
|
|
#include "app.h"
|
|
#include "spooler.h"
|
|
#include "notation.h"
|
|
#include "nttmgr.h"
|
|
|
|
#if defined(TRACE_ROUTE_PACKET)
|
|
static BitTrace Route_Packet("Route Packet");
|
|
# define SET_ROUTE_PACKET() Route_Packet.Set()
|
|
# define CLEAR_ROUTE_PACKET() Route_Packet.Clear()
|
|
#else
|
|
# define SET_ROUTE_PACKET()
|
|
# define CLEAR_ROUTE_PACKET()
|
|
#endif
|
|
|
|
const NetworkAddress NullNetworkAddress = 0x00000000;// 0.0.0.0 (in TCP land);
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
NetworkClient::SharedData
|
|
NetworkClient::DefaultData(
|
|
NetworkClient::GetClassDerivations(),
|
|
NetworkClient::GetMessageHandlers()
|
|
);
|
|
|
|
//
|
|
//#############################################################################
|
|
// Code for the network client class
|
|
//#############################################################################
|
|
//
|
|
Derivation* NetworkClient::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(Receiver::GetClassDerivations(), "NetworkClient");
|
|
return &classDerivations;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Constructor for the NetworkClient
|
|
//
|
|
NetworkClient::NetworkClient(
|
|
ClassID class_ID,
|
|
SharedData &virtual_data,
|
|
ClientID client_ID
|
|
):
|
|
Receiver(class_ID, virtual_data)
|
|
{
|
|
clientID = client_ID;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Destructor for the NetworkClient
|
|
//
|
|
NetworkClient::~NetworkClient()
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Destructor for the NetworkClient
|
|
//
|
|
Logical
|
|
NetworkClient::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Receive packet for the NetworkClient
|
|
//
|
|
void
|
|
NetworkClient::ReceiveNetworkPacket(
|
|
NetworkPacket *packet,
|
|
Receiver::Message *packet_message
|
|
)
|
|
{
|
|
//
|
|
// The recording tee.
|
|
//
|
|
// Every network client comes through here - the interest manager
|
|
// carrying entity updates and the network manager carrying mission
|
|
// control - which are the two places the review build spools
|
|
// separately. One hook covers both, and it sits before Dispatch so a
|
|
// packet is kept whether or not anything downstream makes use of it.
|
|
//
|
|
// The recorder arms itself on the first packet rather than at the
|
|
// green light: playback rebuilds the world from the LoadMission and
|
|
// RunMission packets, so a spool that starts at the flag cannot be
|
|
// replayed.
|
|
//
|
|
if (Application::IsRecording())
|
|
{
|
|
SpoolRecorder *recorder = SpoolRecorder_Get();
|
|
|
|
if (!recorder->IsArmed())
|
|
{
|
|
if (!recorder->Arm())
|
|
{
|
|
//
|
|
// Could not reserve the buffer - say so once, by turning
|
|
// the request off, rather than asking again every packet.
|
|
//
|
|
Application::SetRecording(False);
|
|
}
|
|
}
|
|
recorder->Record(packet);
|
|
}
|
|
Dispatch(packet_message);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Code for the network manager class
|
|
//#############################################################################
|
|
|
|
const Receiver::HandlerEntry
|
|
NetworkManager::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(NetworkManager,ReceiveEggFile)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& NetworkManager::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(NetworkManager::MessageHandlerEntries), NetworkManager::MessageHandlerEntries, NetworkClient::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
NetworkManager::SharedData
|
|
NetworkManager::DefaultData(
|
|
NetworkManager::GetClassDerivations(),
|
|
NetworkManager::GetMessageHandlers()
|
|
);
|
|
|
|
Derivation* NetworkManager::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(NetworkClient::GetClassDerivations(), "NetworkManager");
|
|
return &classDerivations;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Constructor for the NetworkManager
|
|
//
|
|
NetworkManager::NetworkManager(SharedData &shared_data):
|
|
NetworkClient(NetworkManagerClassID, shared_data, NetworkManagerClientID)
|
|
{
|
|
// basic init goes here
|
|
gameID = 0;
|
|
addresses = NULL;
|
|
num_addresses = 0;
|
|
eggTempBuffer = NULL;
|
|
networkEggNotationFile = NULL;
|
|
eggTempNext = 0;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Creates a host for us. Any derived class must deal with reading the
|
|
// notation file to get the network nodes to connect to. This start is
|
|
// designed for STAND-ALONE MODE ONLY and should not be inherited for use by
|
|
//
|
|
void
|
|
NetworkManager::StartConnecting(Mission *)
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//---------------------------------------------------------
|
|
// Make the local host, and get the application to adopt it
|
|
//---------------------------------------------------------
|
|
//
|
|
SOCKADDR_IN address;
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.S_un.S_addr = 1;
|
|
Host *local_host = new Host(1, GameMachineHostType, &address);
|
|
Register_Object(local_host);
|
|
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptLocalHost(local_host);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Now, since this host creator is for stand-alone mode, send the load
|
|
// mission message to the application
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
Application::Message
|
|
load_message(
|
|
Application::LoadMissionMessageID,
|
|
sizeof(Application::Message)
|
|
);
|
|
application->Post(DefaultEventPriority, application, &load_message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::EndMission This routine is called when a mission ends to
|
|
// allow the network management system to do stuff (like disconnecting network
|
|
// connections between pods and so on)
|
|
//
|
|
Logical
|
|
NetworkManager::Shutdown()
|
|
{
|
|
//
|
|
// If there is a notation file floating about, kill it!
|
|
//
|
|
if (networkEggNotationFile)
|
|
{
|
|
Unregister_Object(networkEggNotationFile);
|
|
delete networkEggNotationFile;
|
|
}
|
|
|
|
//
|
|
// Deregister local host
|
|
//
|
|
Host *local_host;
|
|
|
|
local_host = application->GetHostManager()->OrphanLocalHost();
|
|
Unregister_Object(local_host);
|
|
delete local_host;
|
|
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Destructor for the NetworkManager
|
|
//
|
|
NetworkManager::~NetworkManager()
|
|
{
|
|
// shutdown net goes here
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::ReceiveEggFileMessageHandler
|
|
//
|
|
void
|
|
NetworkManager::ReceiveEggFileMessageHandler(
|
|
ReceiveEggFileMessage* EggMessage
|
|
)
|
|
{
|
|
//
|
|
// An egg sequence number of -1 means that the egg is posted locally, and
|
|
// should just call create mission
|
|
//
|
|
if (EggMessage->sequenceNumber == -1)
|
|
{
|
|
application->CreateMission(networkEggNotationFile);
|
|
return;
|
|
}
|
|
|
|
if (EggMessage->sequenceNumber == 0)
|
|
{
|
|
//
|
|
// Make a buffer
|
|
//
|
|
eggTempBuffer = new char[EggMessage->notationFileLength];
|
|
Register_Pointer(eggTempBuffer);
|
|
eggTempNext = 0;
|
|
}
|
|
|
|
//
|
|
// Write the egg data into the buffer
|
|
//
|
|
memcpy(
|
|
(eggTempBuffer+eggTempNext),
|
|
EggMessage->notationData,
|
|
EggMessage->thisMessageLength
|
|
);
|
|
eggTempNext += EggMessage->thisMessageLength;
|
|
|
|
//
|
|
// If we don't have all the data, return and wait for more
|
|
//
|
|
if(eggTempNext < EggMessage->notationFileLength)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We've got all the data, make the notation file
|
|
//
|
|
networkEggNotationFile = new NotationFile();
|
|
Register_Object(networkEggNotationFile);
|
|
networkEggNotationFile->ReadText(eggTempBuffer, eggTempNext);
|
|
networkEggNotationFile->WriteFile("last-loaded.egg");
|
|
|
|
//
|
|
// Now turn the notation file into a mission
|
|
//
|
|
application->CreateMission(networkEggNotationFile);
|
|
|
|
//
|
|
// Get rid of the ram buffer now that we're done with it
|
|
//
|
|
Unregister_Pointer(eggTempBuffer);
|
|
delete eggTempBuffer;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::Send Handles sending a message to a specific network address
|
|
// which can NOT be us. Default behavior is to do nothing, as no network is
|
|
// hooked up if we get here
|
|
//
|
|
void
|
|
NetworkManager::Send(
|
|
Message *,
|
|
ClientID,
|
|
HostID
|
|
)
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::Broadcast Handles broadcasting a message to everyone,
|
|
// including ourselves. !!!! Reliable broadcasting is currently used,
|
|
// implimented by sending a point-to-point message to every destination.
|
|
//
|
|
void
|
|
NetworkManager::Broadcast(
|
|
Message *message,
|
|
ClientID client_id
|
|
)
|
|
{
|
|
//
|
|
// Send this message back to ourselves
|
|
//
|
|
NetworkClient *client = GetNetworkClientPointer(client_id);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, message);
|
|
|
|
//
|
|
// Broadcast the message to everyone else on the network
|
|
//
|
|
ExclusiveBroadcast(message,client_id);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::ExclusiveBroadcast Broadcasts a message to everyone on the
|
|
// network execpt us. Default behavior sends it to nobody, as there is really
|
|
// no network
|
|
//
|
|
void
|
|
NetworkManager::ExclusiveBroadcast(
|
|
Message *,
|
|
ClientID
|
|
)
|
|
{
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::GetNetworkClientPointer Determine who the client of a
|
|
// message is and return a pointer to them.
|
|
//
|
|
NetworkClient*
|
|
NetworkManager::GetNetworkClientPointer(ClientID client_id)
|
|
{
|
|
switch (client_id)
|
|
{
|
|
case NetworkClient::NetworkManagerClientID:
|
|
return this;
|
|
|
|
case NetworkClient::EntityManagerClientID:
|
|
Check(application);
|
|
return application->GetEntityManager();
|
|
|
|
case NetworkClient::HostManagerClientID:
|
|
Check(application);
|
|
return application->GetHostManager();
|
|
|
|
case NetworkClient::InterestManagerClientID:
|
|
Check(application);
|
|
return application->GetInterestManager();
|
|
|
|
case NetworkClient::IcomManagerClientID:
|
|
Check(application);
|
|
return application->GetIntercomManager();
|
|
|
|
case NetworkClient::ApplicationClientID:
|
|
Check(application);
|
|
return application;
|
|
|
|
default:
|
|
DEBUG_STREAM << "------UNKNOWN NETWORK CLIENT ID " << client_id << std::endl << std::flush;
|
|
Fail("Unknown Client ID");
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::RoutePacket
|
|
//
|
|
Logical
|
|
NetworkManager::RoutePacket()
|
|
{
|
|
Check(this);
|
|
|
|
Byte
|
|
message_buffer[NETWORKMANAGER_BUFFER_SIZE];
|
|
NetworkPacket
|
|
*p;
|
|
|
|
//
|
|
// Return if check buffers doesn't have a packet for us
|
|
//
|
|
if (!CheckBuffers((NetworkPacket*)message_buffer))
|
|
{
|
|
return False;
|
|
}
|
|
|
|
//
|
|
// Process and route the packet properly
|
|
//
|
|
p = (NetworkPacket*)message_buffer;
|
|
|
|
if (p->gameID == gameID)
|
|
{
|
|
// cout<<"++++++++client "<<p->clientID<<" gameID "<<p->gameID<<" fromHost "<<p->fromHost<<"\n";
|
|
NetworkClient *client = GetNetworkClientPointer(p->clientID);
|
|
|
|
Check(client);
|
|
SET_ROUTE_PACKET();
|
|
client->ReceiveNetworkPacket(p, &p->messageData);
|
|
CLEAR_ROUTE_PACKET();
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "+++++++++ client " << p->clientID << " gameID " << p->gameID << " fromHost " << p->fromHost << std::endl << std::flush;
|
|
Fail("######### NOT A PACKET FOR THIS GAME!");
|
|
}
|
|
|
|
RemovePacket(p);
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::ExecuteBackground Allows the network to perform tasks in
|
|
// the applications background. The method should return True if the manager
|
|
// is busy, false otherwise. For example, if the input and output buffers of
|
|
// the network driver are not empty then return True, otherwise return False.
|
|
//
|
|
Logical
|
|
NetworkManager::ExecuteBackground()
|
|
{
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::CheckBuffers Checks to see if there is a message for us
|
|
// buffered up in the network interface card and returns a pointer to it.
|
|
//
|
|
Logical
|
|
NetworkManager::CheckBuffers(NetworkPacket*)
|
|
{
|
|
Fail("NetworkManager::CheckBuffers - should never reach here!");
|
|
return False;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::RemovePacket If the network implimentation requires us to
|
|
// free up a packet buffer, this routine would do it.
|
|
//
|
|
void
|
|
NetworkManager::RemovePacket(NetworkPacket*)
|
|
{
|
|
Fail("NetworkManager::RemovePacket - should never reach here!");
|
|
}
|