diff --git a/MUNGA/SPOOLER.cpp b/MUNGA/SPOOLER.cpp index 90a708c..888cc58 100644 --- a/MUNGA/SPOOLER.cpp +++ b/MUNGA/SPOOLER.cpp @@ -197,6 +197,7 @@ SpoolRecorder::SpoolRecorder(): bufferSize(0), armed(False), full(False), + headerWritten(False), packetsRecorded(0) { } @@ -235,6 +236,7 @@ Logical spool->spoolState = SpoolFile::Spooling; armed = True; full = False; + headerWritten = False; packetsRecorded = 0; DEBUG_STREAM << "Record: armed, " << (bufferSize / (1024 * 1024)) @@ -388,6 +390,7 @@ void bufferSize = 0; armed = False; full = False; + headerWritten = False; packetsRecorded = 0; } diff --git a/MUNGA/SPOOLER.h b/MUNGA/SPOOLER.h index ca19126..6c3ade6 100644 --- a/MUNGA/SPOOLER.h +++ b/MUNGA/SPOOLER.h @@ -94,12 +94,30 @@ public: IsArmed() const { return armed; } + // + // A spool opens with a header - the application ID, the resource + // major version, and one (remote, hostID) pair per host named in the + // egg - and playback refuses a spool without it. All of that is + // network-layer knowledge, so it is written by the network manager + // once the hosts exist, and these three are what let it. + // + SpoolFile* + GetSpool() + { return spool; } + Logical + HeaderWritten() const + { return headerWritten; } + void + MarkHeaderWritten() + { headerWritten = True; } + protected: char *buffer; SpoolFile *spool; size_t bufferSize; Logical armed; Logical full; + Logical headerWritten; int packetsRecorded; }; diff --git a/MUNGA_L4/L4NET.CPP b/MUNGA_L4/L4NET.CPP index d25637c..6716692 100644 --- a/MUNGA_L4/L4NET.CPP +++ b/MUNGA_L4/L4NET.CPP @@ -24,6 +24,7 @@ #include "..\munga\appmsg.h" #include "..\munga\mission.h" #include "..\munga\notation.h" +#include "..\munga\spooler.h" //#include @@ -567,6 +568,85 @@ void nextOpenHostID++; } + // + // A recording starts with a header, and this is where it can be written. + // + // A spool opens with the application ID, the resource major version, + // and one (remote, hostID) pair per host named in the egg - playback + // reads them straight back in L4PlaybackNetworkManager::StartConnecting + // and refuses the file without them. The first recordings this build + // made were packets only, so playback met a zero where the application + // ID should be and said "Not a spool file for this application". + // + // It goes here rather than in the recorder because every part of it is + // network-layer knowledge, and here is the moment all of it first + // exists: the hosts have just been created and the mission is in hand. + // + if (Application::IsRecording()) + { + SpoolRecorder *recorder = SpoolRecorder_Get(); + + if (!recorder->IsArmed()) + { + recorder->Arm(); + } + if (recorder->IsArmed() && !recorder->HeaderWritten()) + { + SpoolFile *header_spool = recorder->GetSpool(); + ResourceFile *res_file = application->GetResourceFile(); + Check(res_file); + + *(ApplicationID*)header_spool->GetPointer() = + application->GetApplicationID(); + header_spool->AdvancePointer(sizeof(ApplicationID)); + + int major_version = res_file->versionArray[1]; + + *(int*)header_spool->GetPointer() = major_version; + header_spool->AdvancePointer(sizeof(major_version)); + + HostManager *header_host_mgr = application->GetHostManager(); + Check(header_host_mgr); + Mission::HostIterator header_iterator(mission); + MissionHostData *header_host_data; + int header_host_count = 0; + + while ((header_host_data = header_iterator.ReadAndNext()) != NULL) + { + CString header_name(header_host_data->GetAddressString()); + SOCKADDR_IN header_address; + + NetTransport_Get()->Resolve((LPSTR)header_name, &header_address); + if (header_address.sin_port == 0) + { + header_address.sin_port = htons(localGamePort); + } + + Host *header_host = header_host_mgr->FindHost(header_address); + + // + // Same guard as the arcade spooler needs: a host can be in + // the egg without being connected, and the pair still has to + // be written or every entry after it shifts. + // + *(Logical*)header_spool->GetPointer() = (header_host == NULL) + ? True + : (header_host != header_host_mgr->GetLocalHost()); + header_spool->AdvancePointer(sizeof(Logical)); + + *(HostID*)header_spool->GetPointer() = (header_host == NULL) + ? (HostID) 0 + : header_host->GetHostID(); + header_spool->AdvancePointer(sizeof(HostID)); + ++header_host_count; + } + + recorder->MarkHeaderWritten(); + DEBUG_STREAM << "Record: spool header written, " << header_host_count + << " host(s) from the egg\n" << std::flush; + } + } + // // All the hosts are created, the connects/listens done. // diff --git a/MUNGA_L4/L4SPLR.cpp b/MUNGA_L4/L4SPLR.cpp index 8bff86a..54ecc9e 100644 --- a/MUNGA_L4/L4SPLR.cpp +++ b/MUNGA_L4/L4SPLR.cpp @@ -200,6 +200,42 @@ void L4PlaybackNetworkManager::L4PlaybackNetworkManager(): NetworkManager(L4PlaybackNetworkManager::DefaultData) { + // + // 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(); + + if (egg_name == NULL || strlen(egg_name) == 0) + { + DEBUG_STREAM << "Playback: no egg given. A spool records the race but" + << " not the track it was run on, so -egg must name the one it" + << " was recorded with.\n" << std::flush; + return; + } + + DEBUG_STREAM << "Playback: loading world from egg '" << egg_name + << "'\n" << std::flush; + + networkEggNotationFile = new NotationFile(egg_name); + Register_Object(networkEggNotationFile); + + // + // 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); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,6 +307,26 @@ void 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; + } } }