From c17da32dbdd308b66d649ed0b71237a74ca8e01a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 23 Sep 2016 00:45:41 +0200 Subject: [PATCH] - added object deserialization. It seems to work, at least the stuff I sampled looked like it was properly reatored and it triggers no error condition. - always make the top level object randomaccess when opening a JSON file for reading. Some things won't work right if this is opened for sequential access. --- src/dobject.h | 1 + src/dthinker.h | 2 +- src/g_game.cpp | 4 +- src/g_level.cpp | 2 - src/menu/loadsavemenu.cpp | 4 +- src/serializer.cpp | 136 +++++++++++++++++++++++++++++++++++--- src/serializer.h | 23 +++++-- src/zzz_old.cpp | 5 -- 8 files changed, 149 insertions(+), 28 deletions(-) diff --git a/src/dobject.h b/src/dobject.h index 51102bc4c2..538c7aba49 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -209,6 +209,7 @@ enum EObjectFlags OF_JustSpawned = 1 << 8, // Thinker was spawned this tic OF_SerialSuccess = 1 << 9, // For debugging Serialize() calls OF_Sentinel = 1 << 10, // Object is serving as the sentinel in a ring list + OF_LoadedPlayer = 1 << 11, // this gets flagged during deserialization so that the player checks in there can be simplified. }; template class TObjPtr; diff --git a/src/dthinker.h b/src/dthinker.h index c6a27e3a67..3f3a37b783 100644 --- a/src/dthinker.h +++ b/src/dthinker.h @@ -88,6 +88,7 @@ public: static void MarkRoots(); static DThinker *FirstThinker (int statnum); + static bool bSerialOverride; private: enum no_link_type { NO_LINK }; @@ -100,7 +101,6 @@ private: static FThinkerList Thinkers[MAX_STATNUM+2]; // Current thinkers static FThinkerList FreshThinkers[MAX_STATNUM+1]; // Newly created thinkers - static bool bSerialOverride; friend struct FThinkerList; friend class FThinkerIterator; diff --git a/src/g_game.cpp b/src/g_game.cpp index ef104d67bb..3147f694dd 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1857,7 +1857,7 @@ void G_DoLoadGame () void *data = info->CacheLump(); FSerializer arc; - if (!arc.OpenReader((const char *)data, info->LumpSize, true)) + if (!arc.OpenReader((const char *)data, info->LumpSize)) { Printf("Failed to access savegame info\n"); delete resfile; @@ -1935,7 +1935,7 @@ void G_DoLoadGame () } data = info->CacheLump(); - if (!arc.OpenReader((const char *)data, info->LumpSize, true)) + if (!arc.OpenReader((const char *)data, info->LumpSize)) { Printf("Failed to access savegame info\n"); delete resfile; diff --git a/src/g_level.cpp b/src/g_level.cpp index aa521ff008..de27248497 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1517,8 +1517,6 @@ void G_UnSnapshotLevel (bool hubLoad) FSerializer arc; if (!arc.OpenReader(&level.info->Snapshot)) return; - //if (hubLoad) arc.SetHubTravel (); // no idea if this is still needed. - G_SerializeLevel (arc, hubLoad); level.FromSnapshot = true; diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp index c33de37250..99c34ab3e4 100644 --- a/src/menu/loadsavemenu.cpp +++ b/src/menu/loadsavemenu.cpp @@ -242,7 +242,7 @@ void DLoadSaveMenu::ReadSaveStrings () } void *data = info->CacheLump(); FSerializer arc; - if (arc.OpenReader((const char *)data, info->LumpSize, true)) + if (arc.OpenReader((const char *)data, info->LumpSize)) { int savever = 0; FString engine; @@ -537,7 +537,7 @@ void DLoadSaveMenu::ExtractSaveData (int index) } void *data = info->CacheLump(); FSerializer arc; - if (arc.OpenReader((const char *)data, info->LumpSize, true)) + if (arc.OpenReader((const char *)data, info->LumpSize)) { FString time, pcomment, comment; diff --git a/src/serializer.cpp b/src/serializer.cpp index 251c9a0f45..e8017aba09 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -27,6 +27,7 @@ #include "po_man.h" #include "v_font.h" #include "w_zip.h" +#include "doomerrors.h" char nulspace[1024 * 1024 * 4]; @@ -196,12 +197,15 @@ struct FReader { TArray mObjects; rapidjson::Document mDoc; + TArray mDObjects; + int mPlayers[MAXPLAYERS]; FReader(const char *buffer, size_t length, bool randomaccess) { rapidjson::Document doc; mDoc.Parse(buffer, length); mObjects.Push(FJSONObject(&mDoc, randomaccess)); + memset(mPlayers, 0, sizeof(mPlayers)); } rapidjson::Value *FindKey(const char *key) @@ -258,10 +262,11 @@ bool FSerializer::OpenWriter(bool pretty) // //========================================================================== -bool FSerializer::OpenReader(const char *buffer, size_t length, bool randomaccess) +bool FSerializer::OpenReader(const char *buffer, size_t length) { if (w != nullptr || r != nullptr) return false; - r = new FReader(buffer, length, randomaccess); + r = new FReader(buffer, length, true); + ReadObjects(); return true; } @@ -271,23 +276,23 @@ bool FSerializer::OpenReader(const char *buffer, size_t length, bool randomacces // //========================================================================== -bool FSerializer::OpenReader(FCompressedBuffer *input, bool randomaccess) +bool FSerializer::OpenReader(FCompressedBuffer *input) { if (input->mSize <= 0 || input->mBuffer == nullptr) return false; if (w != nullptr || r != nullptr) return false; if (input->mMethod == METHOD_STORED) { - r = new FReader((char*)input->mBuffer, input->mSize, randomaccess); - return true; + r = new FReader((char*)input->mBuffer, input->mSize, true); } else { char *unpacked = new char[input->mSize]; input->Decompress(unpacked); - r = new FReader(unpacked, input->mSize, randomaccess); - return true; + r = new FReader(unpacked, input->mSize, true); } + ReadObjects(); + return true; } //========================================================================== @@ -400,7 +405,7 @@ bool FSerializer::BeginObject(const char *name, bool randomaccess) // //========================================================================== -void FSerializer::EndObject() +void FSerializer::EndObject(bool endwarning) { if (isWriting()) { @@ -416,6 +421,13 @@ void FSerializer::EndObject() } else { + if (endwarning && !r->mObjects.Last().mRandomAccess) + { + if (r->mObjects.Last().mIterator != r->mObjects.Last().mObject->MemberEnd()) + { + I_Error("Incomplete read of sequential object"); + } + } r->mObjects.Pop(); } } @@ -482,6 +494,22 @@ void FSerializer::EndArray() } } +//========================================================================== +// +// Discards an entry (only needed for sequential access) +// +//========================================================================== + +FSerializer &FSerializer::Discard(const char *key) +{ + if (isReading()) + { + // just get the key and advance the iterator, if present + if (!r->mObjects.Last().mRandomAccess) r->FindKey(key); + } + return *this; +} + //========================================================================== // // Special handler for args (because ACS specials' arg0 needs special treatment.) @@ -760,6 +788,94 @@ void FSerializer::WriteObjects() } } +//========================================================================== +// +// Writes out all collected objects +// +//========================================================================== + +void FSerializer::ReadObjects() +{ + bool founderrors = false; + + if (isReading() && BeginArray("objects")) + { + // Do not link any thinker that's being created here. This will be done by deserializing the thinker list later. + try + { + DThinker::bSerialOverride = true; + r->mDObjects.Resize(ArraySize()); + // First create all the objects + for (unsigned i = 0; i < r->mDObjects.Size(); i++) + { + if (BeginObject(nullptr)) + { + FString clsname; // do not deserialize the class type directly so that we can print appropriate errors. + + Serialize(*this, "classtype", clsname, nullptr); + PClass *cls = PClass::FindClass(clsname); + if (cls == nullptr) + { + Printf("Unknown object class '%d' in savegame", clsname.GetChars()); + founderrors = true; + } + else + { + r->mDObjects[i] = cls->CreateNew(); + } + EndObject(); + } + } + // Now that everything has been created and we can retrieve the pointers we can deserialize it. + + if (!founderrors) + { + // Reset to start; + r->mObjects.Last().mIndex = 0; + + for (unsigned i = 0; i < r->mDObjects.Size(); i++) + { + if (BeginObject(nullptr)) + { + Discard("classtype"); + + int pindex = -1; + Serialize(*this, "playerindex", pindex, nullptr); + auto obj = r->mDObjects[i]; + if (pindex >= 0 && pindex < MAXPLAYERS) + { + obj->ObjectFlags |= OF_LoadedPlayer; + r->mPlayers[pindex] = int(i); + } + obj->SerializeUserVars(*this); + obj->Serialize(*this); + try + { + EndObject(true); + } + catch (CRecoverableError &err) + { + I_Error("%s\n while restoring %s", err.GetMessage(), obj->GetClass()->TypeName.GetChars()); + } + } + } + } + EndArray(); + DThinker::bSerialOverride = false; + } + catch(...) + { + // make sure this flag gets unset, even if something in here throws an error. + DThinker::bSerialOverride = false; + throw; + } + } + if (founderrors) + { + I_Error("Failed to restore all objects in savegame"); + } +} + //========================================================================== // // @@ -1209,7 +1325,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe auto val = arc.r->FindKey(key); if (val != nullptr) { - if (val->IsObject()) + if (val->IsArray()) { const rapidjson::Value &nameval = (*val)[0]; const rapidjson::Value &typeval = (*val)[1]; @@ -1251,7 +1367,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje if (retcode) *retcode = true; if (arc.isWriting()) { - if (value != nullptr) + if (value != nullptr && !(value->ObjectFlags & OF_EuthanizeMe)) { int ndx; if (value == WP_NOCHANGE) diff --git a/src/serializer.h b/src/serializer.h index 8b05cf9ec5..5ff33dbddc 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -16,7 +16,16 @@ struct FReader; extern TArray loadsectors; extern TArray loadlines; extern TArray loadsides; -extern char nulspace[]; + +inline bool nullcmp(const void *buffer, size_t length) +{ + const char *p = (const char *)buffer; + for (; length > 0; length--) + { + if (*p++ != 0) return false; + } + return true; +} struct NumericValue { @@ -51,6 +60,8 @@ public: int ArraySize(); void WriteKey(const char *key); + void WriteObjects(); + void ReadObjects(); public: @@ -59,18 +70,18 @@ public: Close(); } bool OpenWriter(bool pretty = true); - bool OpenReader(const char *buffer, size_t length, bool randomaccess = false); - bool OpenReader(FCompressedBuffer *input, bool randomaccess = false); + bool OpenReader(const char *buffer, size_t length); + bool OpenReader(FCompressedBuffer *input); void Close(); bool BeginObject(const char *name, bool randomaccess = false); - void EndObject(); + void EndObject(bool endwarning = false); bool BeginArray(const char *name); void EndArray(); - void WriteObjects(); unsigned GetSize(const char *group); const char *GetKey(); const char *GetOutput(unsigned *len = nullptr); FCompressedBuffer GetCompressedOutput(); + FSerializer &Discard(const char *key); FSerializer &Args(const char *key, int *args, int *defargs, int special); FSerializer &Terrain(const char *key, int &terrain, int *def = nullptr); FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def); @@ -104,7 +115,7 @@ public: template FSerializer &Array(const char *key, T *obj, int count, bool fullcompare = false) { - if (fullcompare && isWriting() && !memcmp(obj, nulspace, count * sizeof(T))) + if (fullcompare && isWriting() && nullcmp(obj, count * sizeof(T))) { return *this; } diff --git a/src/zzz_old.cpp b/src/zzz_old.cpp index f2ce37bfde..387d40e967 100644 --- a/src/zzz_old.cpp +++ b/src/zzz_old.cpp @@ -174,9 +174,6 @@ void DThinker::SerializeAll(FArchive &arc, bool hubLoad) } else { - // Prevent the constructor from inserting thinkers into a list. - bSerialOverride = true; - try { arc << statcount; @@ -213,11 +210,9 @@ void DThinker::SerializeAll(FArchive &arc, bool hubLoad) } catch (class CDoomError &) { - bSerialOverride = false; DestroyAllThinkers(); throw; } - bSerialOverride = false; } } #endif