The recording tee, which is what the Live Cam was for. L4SpoolingApplication turned out not to be the obstacle it looked like. The review build already records by teeing - it spools each packet and then hands it on - and what tied that to a review build was never the recording but where the buffer came from. MissionReviewApplicationManager is only a pool allocator, and SpoolFile takes whatever buffer it is handed, so SpoolRecorder owns one buffer and needs none of it. One hook covers what the review build taps in two places. Both InterestManager and NetworkManager derive from NetworkClient, so NetworkClient::ReceiveNetworkPacket sees entity updates and mission control alike, and it sits before Dispatch so a packet is kept whether or not anything downstream wants it. The recorder arms on the first packet rather than at the green light, because playback rebuilds the world from the LoadMission and RunMission packets and a spool that starts at the flag cannot be replayed. Two things a live recorder must do that the review one did not. It must not touch the packet. The spooler restamps in place with local arrival time, which is right in itself - playback paces off those stamps and packets from different senders carry different clock origins - but the sender's timestamp is what Simulation::ReadUpdateRecord hands to RP412NETCLOCK and from there to the projection. Overwriting it live would feed arrival jitter into where remote pods are drawn, which is the tick just fixed. So the write position is taken first and the COPY is stamped, in the spool, afterwards. And it must not take the race down. SpoolFile::SpoolPacket answers a full buffer with PostQuitMessage - a fair end to a replay, and killing the race being recorded on a live host. The recorder checks the room first and stops, and says so. RP412RECORDSIZE defaults to 100MB rather than the review build's 6, on Cyd's call: a full grid sends around 17KB a second, so six megabytes is six minutes and a hundred is an hour and a half, which costs nothing on any machine that can run this. Saved at the buzzer - the first of the two StopMissions, the end of the race rather than the fade timer - into SPOOLS\<timestamp>.spl and copied to last.spl, matching where the review build looks. 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.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!");
|
|
}
|