From e37c19b5b449b1dcf594f16640b565f7b9a35d7d Mon Sep 17 00:00:00 2001 From: Boondorl Date: Mon, 19 Feb 2024 13:35:01 -0500 Subject: [PATCH] New API for assigning unique network ids to objects --- src/common/objects/dobject.cpp | 80 ++++++++++++++++++++++++++++- src/common/objects/dobject.h | 11 ++++ src/common/objects/dobjgc.h | 1 + src/d_main.cpp | 2 + src/d_net.cpp | 83 +++++++++++++++++++++++++++++++ src/d_net.h | 23 +++++++++ src/g_game.cpp | 1 + src/p_saveg.cpp | 10 ++++ src/playsim/actor.h | 1 + src/playsim/p_mobj.cpp | 19 +++++++ wadsrc/static/zscript/doombase.zs | 3 ++ 11 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/common/objects/dobject.cpp b/src/common/objects/dobject.cpp index ba4f0a0f1a..09bea79993 100644 --- a/src/common/objects/dobject.cpp +++ b/src/common/objects/dobject.cpp @@ -218,6 +218,8 @@ CCMD (dumpclasses) // //========================================================================== +#include "d_net.h" + void DObject::InPlaceConstructor (void *mem) { new ((EInPlace *)mem) DObject; @@ -317,6 +319,8 @@ void DObject::Release() void DObject::Destroy () { + NetworkEntityManager::RemoveNetworkEntity(this); + // We cannot call the VM during shutdown because all the needed data has been or is in the process of being deleted. if (PClass::bVMOperational) { @@ -504,8 +508,15 @@ void DObject::Serialize(FSerializer &arc) SerializeFlag("justspawned", OF_JustSpawned); SerializeFlag("spawned", OF_Spawned); - + SerializeFlag("networked", OF_Networked); + ObjectFlags |= OF_SerialSuccess; + + if (arc.isReading() && (ObjectFlags & OF_Networked)) + { + ClearNetworkID(); + EnableNetworking(true); + } } void DObject::CheckIfSerialized () const @@ -520,6 +531,73 @@ void DObject::CheckIfSerialized () const } } +//========================================================================== +// +// +// +//========================================================================== + +void DObject::SetNetworkID(const uint32_t id) +{ + if (!IsNetworked()) + { + ObjectFlags |= OF_Networked; + _networkID = id; + } +} + +void DObject::ClearNetworkID() +{ + ObjectFlags &= ~OF_Networked; + _networkID = NetworkEntityManager::WorldNetID; +} + +void DObject::EnableNetworking(const bool enable) +{ + if (enable) + NetworkEntityManager::AddNetworkEntity(this); + else + NetworkEntityManager::RemoveNetworkEntity(this); +} + +static unsigned int GetNetworkID(DObject* const self) +{ + return self->GetNetworkID(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkID, GetNetworkID) +{ + PARAM_SELF_PROLOGUE(DObject); + + ACTION_RETURN_INT(self->GetNetworkID()); +} + +static void EnableNetworking(DObject* const self, const bool enable) +{ + self->EnableNetworking(enable); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, EnableNetworking, EnableNetworking) +{ + PARAM_SELF_PROLOGUE(DObject); + PARAM_BOOL(enable); + + self->EnableNetworking(enable); + return 0; +} + +static DObject* GetNetworkEntity(const unsigned int id) +{ + return NetworkEntityManager::GetNetworkEntity(id); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkEntity, GetNetworkEntity) +{ + PARAM_PROLOGUE; + PARAM_UINT(id); + + ACTION_RETURN_OBJECT(NetworkEntityManager::GetNetworkEntity(id)); +} DEFINE_ACTION_FUNCTION(DObject, MSTime) { diff --git a/src/common/objects/dobject.h b/src/common/objects/dobject.h index aed2230716..bb295fb78e 100644 --- a/src/common/objects/dobject.h +++ b/src/common/objects/dobject.h @@ -351,6 +351,17 @@ protected: friend T* Create(Args&&... args); friend class JitCompiler; + +private: + // This is intentionally left unserialized. + uint32_t _networkID; + +public: + inline bool IsNetworked() const { return (ObjectFlags & OF_Networked); } + inline uint32_t GetNetworkID() const { return _networkID; } + void SetNetworkID(const uint32_t id); + void ClearNetworkID(); + virtual void EnableNetworking(const bool enable); }; // This is the only method aside from calling CreateNew that should be used for creating DObjects diff --git a/src/common/objects/dobjgc.h b/src/common/objects/dobjgc.h index 969551d371..f5514255db 100644 --- a/src/common/objects/dobjgc.h +++ b/src/common/objects/dobjgc.h @@ -26,6 +26,7 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function + OF_Networked = 1 << 14, // Object has a unique network identifier that makes it synchronizable between all clients. }; template class TObjPtr; diff --git a/src/d_main.cpp b/src/d_main.cpp index 98b6fb222c..b0e6826368 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -3100,6 +3100,8 @@ static int FileSystemPrintf(FSMessageLevel level, const char* fmt, ...) static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allwads, std::vector& pwads) { + NetworkEntityManager::InitializeNetworkEntities(); + if (!restart) { V_InitScreenSize(); diff --git a/src/d_net.cpp b/src/d_net.cpp index c43ae158b0..2536739cf7 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2965,6 +2965,89 @@ int Net_GetLatency(int *ld, int *ad) return severity; } +void NetworkEntityManager::InitializeNetworkEntities() +{ + if (!s_netEntities.Size()) + s_netEntities.AppendFill(nullptr, NetIDStart); // Allocate the first 0-8 slots for the world and clients. +} + +// Clients need special handling since they always go in slots 1 - MAXPLAYERS. +void NetworkEntityManager::SetClientNetworkEntity(player_t* const client) +{ + AActor* const mo = client->mo; + const uint32_t id = ClientNetIDStart + mo->Level->PlayerNum(client); + + // If resurrecting, we need to swap the corpse's position with the new pawn's + // position so it's no longer considered the client's body. + DObject* const oldBody = s_netEntities[id]; + if (oldBody != nullptr) + { + if (oldBody == mo) + return; + + const uint32_t curID = mo->GetNetworkID(); + + s_netEntities[curID] = oldBody; + oldBody->ClearNetworkID(); + oldBody->SetNetworkID(curID); + + mo->ClearNetworkID(); + } + else + { + RemoveNetworkEntity(mo); // Free up its current id. + } + + s_netEntities[id] = mo; + mo->SetNetworkID(id); +} + +void NetworkEntityManager::AddNetworkEntity(DObject* const ent) +{ + if (ent->IsNetworked()) + return; + + // Slot 0 is reserved for the world. + // Clients go in the first 1 - MAXPLAYERS slots + // Everything else is first come first serve. + uint32_t id = WorldNetID; + if (s_openNetIDs.Size()) + { + s_openNetIDs.Pop(id); + s_netEntities[id] = ent; + } + else + { + id = s_netEntities.Push(ent); + } + + ent->SetNetworkID(id); +} + +void NetworkEntityManager::RemoveNetworkEntity(DObject* const ent) +{ + if (!ent->IsNetworked()) + return; + + const uint32_t id = ent->GetNetworkID(); + if (id == WorldNetID) + return; + + assert(s_netEntities[id] == ent); + if (id >= NetIDStart) + s_openNetIDs.Push(id); + s_netEntities[id] = nullptr; + ent->ClearNetworkID(); +} + +DObject* NetworkEntityManager::GetNetworkEntity(const uint32_t id) +{ + if (id == WorldNetID || id >= s_netEntities.Size()) + return nullptr; + + return s_netEntities[id]; +} + // [RH] List "ping" times CCMD (pings) { diff --git a/src/d_net.h b/src/d_net.h index f0c1606838..e4029bd660 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -95,6 +95,29 @@ extern int nodeforplayer[MAXPLAYERS]; extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; extern int ticdup; +class player_t; +class DObject; + +class NetworkEntityManager +{ +private: + inline static TArray s_netEntities = {}; + inline static TArray s_openNetIDs = {}; + +public: + NetworkEntityManager() = delete; + + inline static uint32_t WorldNetID = 0u; + inline static uint32_t ClientNetIDStart = 1u; + inline static uint32_t NetIDStart = MAXPLAYERS + 1u; + + static void InitializeNetworkEntities(); + static void SetClientNetworkEntity(player_t* const client); + static void AddNetworkEntity(DObject* const ent); + static void RemoveNetworkEntity(DObject* const ent); + static DObject* GetNetworkEntity(const uint32_t id); +}; + // [RH] // New generic packet structure: // diff --git a/src/g_game.cpp b/src/g_game.cpp index 30abb152e6..1b81ae6b06 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1424,6 +1424,7 @@ void FLevelLocals::PlayerReborn (int player) p->oldbuttons = ~0, p->attackdown = true; p->usedown = true; // don't do anything immediately p->original_oldbuttons = ~0; p->playerstate = PST_LIVE; + NetworkEntityManager::SetClientNetworkEntity(p); if (gamestate != GS_TITLELEVEL) { diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index d524ad1390..733239df20 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -62,6 +62,7 @@ #include "fragglescript/t_script.h" #include "s_music.h" #include "model.h" +#include "d_net.h" EXTERN_CVAR(Bool, save_formatted) @@ -653,6 +654,15 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload); } arc.EndArray(); + + if (!skipload) + { + for (unsigned int i = 0u; i < MAXPLAYERS; ++i) + { + if (PlayerInGame(i) && Players[i]->mo != nullptr) + NetworkEntityManager::SetClientNetworkEntity(Players[i]); + } + } } if (!skipload && numPlayersNow > numPlayers) { diff --git a/src/playsim/actor.h b/src/playsim/actor.h index b355dfb971..2cab5b76df 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -794,6 +794,7 @@ public: virtual void PostSerialize() override; virtual void PostBeginPlay() override; // Called immediately before the actor's first tick virtual void Tick() override; + void EnableNetworking(const bool enable) override; static AActor *StaticSpawn (FLevelLocals *Level, PClassActor *type, const DVector3 &pos, replace_t allowreplacement, bool SpawningMapThing = false); diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 69cecbb489..aada9f88a5 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -100,6 +100,7 @@ #include "a_dynlight.h" #include "fragglescript/t_fs.h" #include "shadowinlines.h" +#include "d_net.h" // MACROS ------------------------------------------------------------------ @@ -178,6 +179,23 @@ IMPLEMENT_POINTERS_START(AActor) IMPLEMENT_POINTER(boneComponentData) IMPLEMENT_POINTERS_END +//========================================================================== +// +// Make sure Actors can never have their networking disabled. +// +//========================================================================== + +void AActor::EnableNetworking(const bool enable) +{ + if (!enable) + { + ThrowAbortException(X_OTHER, "Cannot disable networking on Actors. Consider a Thinker instead."); + return; + } + + Super::EnableNetworking(true); +} + //========================================================================== // // AActor :: Serialize @@ -4775,6 +4793,7 @@ AActor *AActor::StaticSpawn(FLevelLocals *Level, PClassActor *type, const DVecto AActor *actor; actor = static_cast(Level->CreateThinker(type)); + actor->EnableNetworking(true); ConstructActor(actor, pos, SpawningMapThing); return actor; diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 7f8aab0edd..829e752707 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -137,6 +137,9 @@ extend class Object native static void MarkSound(Sound snd); native static uint BAM(double angle); native static void SetMusicVolume(float vol); + native clearscope static Object GetNetworkEntity(uint id); + native play void EnableNetworking(bool enable); + native clearscope uint GetNetworkID() const; } class Thinker : Object native play