diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index a9e0839a9..f3e2c9bcd 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -713,7 +713,6 @@ set (PCH_SOURCES core/savegamehelp.cpp core/quotes.cpp core/screenshot.cpp - core/serializer.cpp core/raze_music.cpp core/raze_sound.cpp @@ -786,9 +785,11 @@ set (PCH_SOURCES common/engine/sc_man.cpp common/engine/palettecontainer.cpp common/engine/stringtable.cpp + common/engine/serializer.cpp common/objects/dobject.cpp common/objects/dobjgc.cpp common/objects/dobjtype.cpp + common/scripting/core/dictionary.cpp common/scripting/core/symbols.cpp common/scripting/core/types.cpp common/scripting/core/scopebarrier.cpp @@ -955,10 +956,9 @@ include_directories( common/console common/engine common/objects - common/scripting/interface - common/scripting/core common/scripting/vm common/scripting/jit + common/scripting/core ${CMAKE_BINARY_DIR}/libraries/gdtoa ${SYSTEM_SOURCES_DIR} @@ -1074,11 +1074,11 @@ source_group("Common\\Engine" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/c source_group("Common\\Objects" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/objects/.+") source_group("Common\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/fonts/.+") source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") -source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") source_group("Common\\Scripting" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/.+") source_group("Common\\Scripting\\Core" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/core/.+") -source_group("Common\\Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/vm/.+") source_group("Common\\Scripting\\JIT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/jit/.+") +source_group("Common\\Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/vm/.+") +source_group("Common\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+") source_group("Common\\Third Party" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/.+") source_group("Common\\Third Party\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/math/.+") source_group("Common\\Third Party\\RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/rapidjson/.+") diff --git a/source/common/engine/serialize_obj.h b/source/common/engine/serialize_obj.h new file mode 100644 index 000000000..c818d08fa --- /dev/null +++ b/source/common/engine/serialize_obj.h @@ -0,0 +1,17 @@ +#pragma once + +// These are in a separate header because they require some rather 'dirty' headers to work which should not be part of serializer.h + +template +FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, TObjPtr *) +{ + Serialize(arc, key, value.o, nullptr); + return arc; +} + +template +FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, T *) +{ + Serialize(arc, key, value.o, nullptr); + return arc; +} diff --git a/source/core/serializer.cpp b/source/common/engine/serializer.cpp similarity index 87% rename from source/core/serializer.cpp rename to source/common/engine/serializer.cpp index 715103acb..ae2f872f9 100644 --- a/source/core/serializer.cpp +++ b/source/common/engine/serializer.cpp @@ -50,12 +50,15 @@ #include "cmdlib.h" #include "utf8.h" #include "printf.h" -#include "raze_sound.h" +#include "s_soundinternal.h" #include "engineerrors.h" #include "textures.h" #include "texturemanager.h" -bool save_full = false; +extern DObject *WP_NOCHANGE; +bool save_full = false; // for testing. Should be removed afterward. + +#include "serializer_internal.h" //========================================================================== // @@ -66,7 +69,7 @@ bool save_full = false; //========================================================================== static TArray out; -static const char *StringToUnicode(const char *cc, int size = -1) +const char *StringToUnicode(const char *cc, int size) { int ch; const uint8_t *c = (const uint8_t*)cc; @@ -94,7 +97,7 @@ static const char *StringToUnicode(const char *cc, int size = -1) return &out[0]; } -static const char *UnicodeToString(const char *cc) +const char *UnicodeToString(const char *cc) { out.Resize((unsigned)strlen(cc) + 1); int ndx = 0; @@ -110,225 +113,6 @@ static const char *UnicodeToString(const char *cc) return &out[0]; } -//========================================================================== -// -// -// -//========================================================================== - -struct FJSONObject -{ - rapidjson::Value *mObject; - rapidjson::Value::MemberIterator mIterator; - int mIndex; - - FJSONObject(rapidjson::Value *v) - { - mObject = v; - if (v->IsObject()) mIterator = v->MemberBegin(); - else if (v->IsArray()) - { - mIndex = 0; - } - } -}; - -//========================================================================== -// -// some wrapper stuff to keep the RapidJSON dependencies out of the global headers. -// FSerializer should not expose any of this. -// -//========================================================================== - -struct FWriter -{ - typedef rapidjson::Writer > Writer; - typedef rapidjson::PrettyWriter > PrettyWriter; - - Writer *mWriter1; - PrettyWriter *mWriter2; - TArray mInObject; - rapidjson::StringBuffer mOutString; - TArray mDObjects; - TMap mObjectMap; - - FWriter(bool pretty) - { - if (!pretty) - { - mWriter1 = new Writer(mOutString); - mWriter2 = nullptr; - } - else - { - mWriter1 = nullptr; - mWriter2 = new PrettyWriter(mOutString); - } - } - - ~FWriter() - { - if (mWriter1) delete mWriter1; - if (mWriter2) delete mWriter2; - } - - - bool inObject() const - { - return mInObject.Size() > 0 && mInObject.Last(); - } - - void StartObject() - { - if (mWriter1) mWriter1->StartObject(); - else if (mWriter2) mWriter2->StartObject(); - } - - void EndObject() - { - if (mWriter1) mWriter1->EndObject(); - else if (mWriter2) mWriter2->EndObject(); - } - - void StartArray() - { - if (mWriter1) mWriter1->StartArray(); - else if (mWriter2) mWriter2->StartArray(); - } - - void EndArray() - { - if (mWriter1) mWriter1->EndArray(); - else if (mWriter2) mWriter2->EndArray(); - } - - void Key(const char *k) - { - if (mWriter1) mWriter1->Key(k); - else if (mWriter2) mWriter2->Key(k); - } - - void Null() - { - if (mWriter1) mWriter1->Null(); - else if (mWriter2) mWriter2->Null(); - } - - void StringU(const char *k, bool encode) - { - if (encode) k = StringToUnicode(k); - if (mWriter1) mWriter1->String(k); - else if (mWriter2) mWriter2->String(k); - } - - void String(const char *k) - { - k = StringToUnicode(k); - if (mWriter1) mWriter1->String(k); - else if (mWriter2) mWriter2->String(k); - } - - void String(const char *k, int size) - { - k = StringToUnicode(k, size); - if (mWriter1) mWriter1->String(k); - else if (mWriter2) mWriter2->String(k); - } - - void Bool(bool k) - { - if (mWriter1) mWriter1->Bool(k); - else if (mWriter2) mWriter2->Bool(k); - } - - void Int(int32_t k) - { - if (mWriter1) mWriter1->Int(k); - else if (mWriter2) mWriter2->Int(k); - } - - void Int64(int64_t k) - { - if (mWriter1) mWriter1->Int64(k); - else if (mWriter2) mWriter2->Int64(k); - } - - void Uint(uint32_t k) - { - if (mWriter1) mWriter1->Uint(k); - else if (mWriter2) mWriter2->Uint(k); - } - - void Uint64(int64_t k) - { - if (mWriter1) mWriter1->Uint64(k); - else if (mWriter2) mWriter2->Uint64(k); - } - - void Double(double k) - { - if (mWriter1) - { - mWriter1->Double(k); - } - else if (mWriter2) - { - mWriter2->Double(k); - } - } - -}; - -//========================================================================== -// -// -// -//========================================================================== - -struct FReader -{ - TArray mObjects; - rapidjson::Document mDoc; - TArray mDObjects; - rapidjson::Value *mKeyValue = nullptr; - bool mObjectsRead = false; - - FReader(const char *buffer, size_t length) - { - mDoc.Parse(buffer, length); - mObjects.Push(FJSONObject(&mDoc)); - } - - rapidjson::Value *FindKey(const char *key) - { - FJSONObject &obj = mObjects.Last(); - - if (obj.mObject->IsObject()) - { - if (key == nullptr) - { - // we are performing an iteration of the object through GetKey. - auto p = mKeyValue; - mKeyValue = nullptr; - return p; - } - else - { - // Find the given key by name; - auto it = obj.mObject->FindMember(key); - if (it == obj.mObject->MemberEnd()) return nullptr; - return &it->value; - } - } - else if (obj.mObject->IsArray() && (unsigned)obj.mIndex < obj.mObject->Size()) - { - return &(*obj.mObject)[obj.mIndex++]; - } - return nullptr; - } -}; - - //========================================================================== // // @@ -402,6 +186,7 @@ void FSerializer::Close() } if (r != nullptr) { + CloseReaderCustom(); delete r; r = nullptr; } @@ -635,6 +420,43 @@ FSerializer &FSerializer::ScriptNum(const char *key, int &num) return *this; } +//========================================================================== +// +// this is merely a placeholder to satisfy the VM's spriteid type. +// +//========================================================================== + +FSerializer &FSerializer::Sprite(const char *key, int32_t &spritenum, int32_t *def) +{ + if (isWriting()) + { + if (w->inObject() && def != nullptr && *def == spritenum) return *this; + WriteKey(key); + w->Int(spritenum); + } + else + { + auto val = r->FindKey(key); + if (val != nullptr && val->IsInt()) + { + spritenum = val->GetInt(); + } + } + return *this; +} + +//========================================================================== +// +// only here so that it can be virtually overridden. Without reference this cannot save anything. +// +//========================================================================== + +FSerializer& FSerializer::StatePointer(const char* key, void* ptraddr, bool *res) +{ + if (res) *res = false; + return *this; +} + //========================================================================== // // @@ -835,7 +657,7 @@ void FSerializer::ReadObjects(bool hubtravel) obj->SerializeUserVars(*this); obj->Serialize(*this); } - catch (CEngineError &err) + catch (CRecoverableError &err) { // In case something in here throws an error, let's continue and deal with it later. Printf(TEXTCOLOR_RED "'%s'\n while restoring %s\n", err.GetMessage(), obj ? obj->GetClass()->TypeName.GetChars() : "invalid object"); @@ -1136,6 +958,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, uint32_t &value, uint3 // //========================================================================== +FSerializer& Serialize(FSerializer& arc, const char* key, char& value, char* defval) +{ + int32_t vv = value; + int32_t vvd = defval ? *defval : value - 1; + Serialize(arc, key, vv, &vvd); + value = (int8_t)vv; + return arc; +} + FSerializer &Serialize(FSerializer &arc, const char *key, int8_t &value, int8_t *defval) { int32_t vv = value; @@ -1229,25 +1060,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *d // //========================================================================== -template -FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **defval, T *base) -{ - assert(base != nullptr); - if (arc.isReading() || !arc.w->inObject() || defval == nullptr || value != *defval) - { - int64_t vv = value == nullptr ? -1 : value - base; - Serialize(arc, key, vv, nullptr); - value = vv < 0 ? nullptr : base + vv; - } - return arc; -} - -//========================================================================== -// -// -// -//========================================================================== - FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval) { if (arc.isWriting()) @@ -1344,13 +1156,11 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje if (value != nullptr && !(value->ObjectFlags & (OF_EuthanizeMe | OF_Transient))) { int ndx; - /* if (value == WP_NOCHANGE) { ndx = -1; } else - */ { int *pndx = arc.w->mObjectMap.CheckKey(value); if (pndx != nullptr) @@ -1391,7 +1201,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje int index = val->GetInt(); if (index == -1) { - value = nullptr;// WP_NOCHANGE; + value = WP_NOCHANGE; } else { @@ -1463,19 +1273,20 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *d //========================================================================== // -// Note that the sound name here is not reliable because it's a file name, not a unique alias. -// Currently the index is the only means to retrieve the sound on loading. +// // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def) { -#if 1 - int id = sid; - Serialize(arc, key, id, nullptr); - if (arc.isReading()) sid = FSoundID(id); -#else - if (arc.isWriting()) + if (!arc.soundNamesAreUnique) + { + //If sound name here is not reliable, we need to save by index instead. + int id = sid; + Serialize(arc, key, id, nullptr); + if (arc.isReading()) sid = FSoundID(id); + } + else if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || sid != *def) { @@ -1507,7 +1318,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI } } } -#endif return arc; } @@ -1625,6 +1435,86 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&fon } +//========================================================================== +// +// Dictionary +// +//========================================================================== + +FString DictionaryToString(const Dictionary &dict) +{ + Dictionary::ConstPair *pair; + Dictionary::ConstIterator i { dict.Map }; + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + writer.StartObject(); + + while (i.NextPair(pair)) + { + writer.Key(pair->Key); + writer.String(pair->Value); + } + + writer.EndObject(); + + FString contents { buffer.GetString(), buffer.GetSize() }; + return contents; +} + +Dictionary *DictionaryFromString(const FString &string) +{ + if (string.Compare("null") == 0) + { + return nullptr; + } + + Dictionary *const dict = Create(); + + if (string.IsEmpty()) + { + return dict; + } + + rapidjson::Document doc; + doc.Parse(string.GetChars(), string.Len()); + + if (doc.GetType() != rapidjson::Type::kObjectType) + { + I_Error("Dictionary is expected to be a JSON object."); + return dict; + } + + for (auto i = doc.MemberBegin(); i != doc.MemberEnd(); ++i) + { + if (i->value.GetType() != rapidjson::Type::kStringType) + { + I_Error("Dictionary value is expected to be a JSON string."); + return dict; + } + + dict->Map.Insert(i->name.GetString(), i->value.GetString()); + } + + return dict; +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, Dictionary *&dict, Dictionary **) +{ + if (arc.isWriting()) + { + FString contents { dict ? DictionaryToString(*dict) : "null" }; + return arc(key, contents); + } + else + { + FString contents; + arc(key, contents); + dict = DictionaryFromString(contents); + return arc; + } +} + //========================================================================== // // Handler to retrieve a numeric value of any kind. diff --git a/source/core/serializer.h b/source/common/engine/serializer.h similarity index 89% rename from source/core/serializer.h rename to source/common/engine/serializer.h index 4a4199b33..517bad7b9 100644 --- a/source/core/serializer.h +++ b/source/common/engine/serializer.h @@ -9,6 +9,7 @@ #include "vectors.h" #include "palentry.h" #include "name.h" +#include "dictionary.h" extern bool save_full; @@ -61,11 +62,14 @@ class FSerializer public: FWriter *w = nullptr; FReader *r = nullptr; + bool soundNamesAreUnique = false; // While in GZDoom, sound names are unique, that isn't universally true - let the serializer handle both cases with a flag. unsigned ArraySize(); void WriteKey(const char *key); void WriteObjects(); +private: + virtual void CloseReaderCustom() {} public: ~FSerializer() @@ -73,6 +77,7 @@ public: mErrors = 0; // The destructor may not throw an exception so silence the error checker. Close(); } + void SetUniqueSoundNames() { soundNamesAreUnique = true; } bool OpenWriter(bool pretty = true); bool OpenReader(const char *buffer, size_t length); bool OpenReader(FCompressedBuffer *input); @@ -86,6 +91,11 @@ public: const char *GetKey(); const char *GetOutput(unsigned *len = nullptr); FCompressedBuffer GetCompressedOutput(); + // The sprite serializer is a special case because it is needed by the VM to handle its 'spriteid' type. + virtual FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def); + // This is only needed by the type system. + virtual FSerializer& StatePointer(const char* key, void* ptraddr, bool *res); + FSerializer &StringPtr(const char *key, const char *&charptr); // This only retrieves the address but creates no permanent copy of the string unlike the regular char* serializer. FSerializer &AddString(const char *key, const char *charptr); const char *GetString(const char *key); @@ -173,6 +183,8 @@ public: int mErrors = 0; }; +FSerializer& Serialize(FSerializer& arc, const char* key, char& value, char* defval); + FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval); FSerializer &Serialize(FSerializer &arc, const char *key, int64_t &value, int64_t *defval); FSerializer &Serialize(FSerializer &arc, const char *key, uint64_t &value, uint64_t *defval); @@ -191,7 +203,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def); FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); - template FSerializer &Serialize(FSerializer &arc, const char *key, T *&value, T **) { @@ -229,6 +240,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, TArray &value, template<> FSerializer& Serialize(FSerializer& arc, const char* key, PClass*& clst, PClass** def); template<> FSerializer& Serialize(FSerializer& arc, const char* key, FFont*& font, FFont** def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, Dictionary *&dict, Dictionary **def); inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector3 &p, DVector3 *def) { @@ -298,4 +310,7 @@ inline SaveRecord::SaveRecord(const char* nm, void (*handler)(FSerializer& arc)) saveRecords.records.Push(this); } +FString DictionaryToString(const Dictionary &dict); +Dictionary *DictionaryFromString(const FString &string); + #endif diff --git a/source/common/engine/serializer_internal.h b/source/common/engine/serializer_internal.h new file mode 100644 index 000000000..7323f23f7 --- /dev/null +++ b/source/common/engine/serializer_internal.h @@ -0,0 +1,240 @@ +const char* UnicodeToString(const char* cc); +const char* StringToUnicode(const char* cc, int size = -1); + +//========================================================================== +// +// +// +//========================================================================== + +struct FJSONObject +{ + rapidjson::Value* mObject; + rapidjson::Value::MemberIterator mIterator; + int mIndex; + + FJSONObject(rapidjson::Value* v) + { + mObject = v; + if (v->IsObject()) mIterator = v->MemberBegin(); + else if (v->IsArray()) + { + mIndex = 0; + } + } +}; + +//========================================================================== +// +// some wrapper stuff to keep the RapidJSON dependencies out of the global headers. +// FSerializer should not expose any of this, although it is needed by special serializers. +// +//========================================================================== + +struct FWriter +{ + typedef rapidjson::Writer > Writer; + typedef rapidjson::PrettyWriter > PrettyWriter; + + Writer *mWriter1; + PrettyWriter *mWriter2; + TArray mInObject; + rapidjson::StringBuffer mOutString; + TArray mDObjects; + TMap mObjectMap; + + FWriter(bool pretty) + { + if (!pretty) + { + mWriter1 = new Writer(mOutString); + mWriter2 = nullptr; + } + else + { + mWriter1 = nullptr; + mWriter2 = new PrettyWriter(mOutString); + } + } + + ~FWriter() + { + if (mWriter1) delete mWriter1; + if (mWriter2) delete mWriter2; + } + + + bool inObject() const + { + return mInObject.Size() > 0 && mInObject.Last(); + } + + void StartObject() + { + if (mWriter1) mWriter1->StartObject(); + else if (mWriter2) mWriter2->StartObject(); + } + + void EndObject() + { + if (mWriter1) mWriter1->EndObject(); + else if (mWriter2) mWriter2->EndObject(); + } + + void StartArray() + { + if (mWriter1) mWriter1->StartArray(); + else if (mWriter2) mWriter2->StartArray(); + } + + void EndArray() + { + if (mWriter1) mWriter1->EndArray(); + else if (mWriter2) mWriter2->EndArray(); + } + + void Key(const char *k) + { + if (mWriter1) mWriter1->Key(k); + else if (mWriter2) mWriter2->Key(k); + } + + void Null() + { + if (mWriter1) mWriter1->Null(); + else if (mWriter2) mWriter2->Null(); + } + + void StringU(const char *k, bool encode) + { + if (encode) k = StringToUnicode(k); + if (mWriter1) mWriter1->String(k); + else if (mWriter2) mWriter2->String(k); + } + + void String(const char *k) + { + k = StringToUnicode(k); + if (mWriter1) mWriter1->String(k); + else if (mWriter2) mWriter2->String(k); + } + + void String(const char *k, int size) + { + k = StringToUnicode(k, size); + if (mWriter1) mWriter1->String(k); + else if (mWriter2) mWriter2->String(k); + } + + void Bool(bool k) + { + if (mWriter1) mWriter1->Bool(k); + else if (mWriter2) mWriter2->Bool(k); + } + + void Int(int32_t k) + { + if (mWriter1) mWriter1->Int(k); + else if (mWriter2) mWriter2->Int(k); + } + + void Int64(int64_t k) + { + if (mWriter1) mWriter1->Int64(k); + else if (mWriter2) mWriter2->Int64(k); + } + + void Uint(uint32_t k) + { + if (mWriter1) mWriter1->Uint(k); + else if (mWriter2) mWriter2->Uint(k); + } + + void Uint64(int64_t k) + { + if (mWriter1) mWriter1->Uint64(k); + else if (mWriter2) mWriter2->Uint64(k); + } + + void Double(double k) + { + if (mWriter1) + { + mWriter1->Double(k); + } + else if (mWriter2) + { + mWriter2->Double(k); + } + } + +}; + +//========================================================================== +// +// +// +//========================================================================== + +struct FReader +{ + TArray mObjects; + rapidjson::Document mDoc; + TArray mDObjects; + rapidjson::Value *mKeyValue = nullptr; + bool mObjectsRead = false; + + FReader(const char *buffer, size_t length) + { + mDoc.Parse(buffer, length); + mObjects.Push(FJSONObject(&mDoc)); + } + + rapidjson::Value *FindKey(const char *key) + { + FJSONObject &obj = mObjects.Last(); + + if (obj.mObject->IsObject()) + { + if (key == nullptr) + { + // we are performing an iteration of the object through GetKey. + auto p = mKeyValue; + mKeyValue = nullptr; + return p; + } + else + { + // Find the given key by name; + auto it = obj.mObject->FindMember(key); + if (it == obj.mObject->MemberEnd()) return nullptr; + return &it->value; + } + } + else if (obj.mObject->IsArray() && (unsigned)obj.mIndex < obj.mObject->Size()) + { + return &(*obj.mObject)[obj.mIndex++]; + } + return nullptr; + } +}; + +//========================================================================== +// +// +// +//========================================================================== + +template +FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **defval, T *base) +{ + assert(base != nullptr); + if (arc.isReading() || !arc.w->inObject() || defval == nullptr || value != *defval) + { + int64_t vv = value == nullptr ? -1 : value - base; + Serialize(arc, key, vv, nullptr); + value = vv < 0 ? nullptr : base + vv; + } + return arc; +} + diff --git a/source/common/scripting/core/dictionary.cpp b/source/common/scripting/core/dictionary.cpp new file mode 100644 index 000000000..b8dacd2bc --- /dev/null +++ b/source/common/scripting/core/dictionary.cpp @@ -0,0 +1,218 @@ +#include "dictionary.h" + +#include "vm.h" +#include "serializer.h" + +#include + +//===================================================================================== +// +// DObject implementations for Dictionary and DictionaryIterator +// +//===================================================================================== + +IMPLEMENT_CLASS(Dictionary, false, false); + +IMPLEMENT_CLASS(DictionaryIterator, false, true); + +IMPLEMENT_POINTERS_START(DictionaryIterator) +IMPLEMENT_POINTER(Dict) +IMPLEMENT_POINTERS_END + +//===================================================================================== +// +// Dictionary functions +// +//===================================================================================== + +void Dictionary::Serialize(FSerializer &arc) +{ + Super::Serialize(arc); + + constexpr char key[] { "dictionary" }; + + if (arc.isWriting()) + { + // Pass this instance to serializer. + Dictionary *pointerToThis = this; + arc(key, pointerToThis); + } + else + { + // Receive new Dictionary, copy contents, clean up. + Dictionary *pointerToDeserializedDictionary; + arc(key, pointerToDeserializedDictionary); + Map.TransferFrom(pointerToDeserializedDictionary->Map); + delete pointerToDeserializedDictionary; + } +} + +static Dictionary *DictCreate() +{ + Dictionary *dict { Create() }; + + return dict; +} + +static void DictInsert(Dictionary *dict, const FString &key, const FString &value) +{ + dict->Map.Insert(key, value); +} + +static void DictAt(const Dictionary *dict, const FString &key, FString *result) +{ + const FString *value = dict->Map.CheckKey(key); + *result = value ? *value : ""; +} + +static void DictToString(const Dictionary *dict, FString *result) +{ + *result = DictionaryToString(*dict); +} + +static void DictRemove(Dictionary *dict, const FString &key) +{ + dict->Map.Remove(key); +} + +//===================================================================================== +// +// Dictionary exports +// +//===================================================================================== + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, Create, DictCreate) +{ + ACTION_RETURN_POINTER(DictCreate()); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, Insert, DictInsert) +{ + PARAM_SELF_STRUCT_PROLOGUE(Dictionary); + PARAM_STRING(key); + PARAM_STRING(value); + DictInsert(self, key, value); + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, At, DictAt) +{ + PARAM_SELF_STRUCT_PROLOGUE(Dictionary); + PARAM_STRING(key); + FString result; + DictAt(self, key, &result); + ACTION_RETURN_STRING(result); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, ToString, DictToString) +{ + PARAM_SELF_STRUCT_PROLOGUE(Dictionary); + FString result; + DictToString(self, &result); + ACTION_RETURN_STRING(result); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, FromString, DictionaryFromString) +{ + PARAM_PROLOGUE; + PARAM_STRING(string); + ACTION_RETURN_POINTER(DictionaryFromString(string)); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_Dictionary, Remove, DictRemove) +{ + PARAM_SELF_STRUCT_PROLOGUE(Dictionary); + PARAM_STRING(key); + DictRemove(self, key); + return 0; +} + +//===================================================================================== +// +// DictionaryIterator functions +// +//===================================================================================== + +DictionaryIterator::DictionaryIterator() + : Iterator(nullptr) + , Pair(nullptr) + , Dict(nullptr) +{ +} + +void DictionaryIterator::Serialize(FSerializer &arc) +{ + if (arc.isWriting()) + { + I_Error("Attempt to save pointer to unhandled type DictionaryIterator"); + } +} + +void DictionaryIterator::init(Dictionary *dict) +{ + Iterator = std::make_unique(dict->Map); + Dict = dict; + + GC::WriteBarrier(this, Dict); +} + +static DictionaryIterator *DictIteratorCreate(Dictionary *dict) +{ + DictionaryIterator *iterator = Create(); + iterator->init(dict); + + return iterator; +} + +static int DictIteratorNext(DictionaryIterator *self) +{ + assert(self->Iterator != nullptr); + + const bool hasNext { self->Iterator->NextPair(self->Pair) }; + return hasNext; +} + +static void DictIteratorKey(const DictionaryIterator *self, FString *result) +{ + *result = self->Pair ? self->Pair->Key : FString {}; +} + +static void DictIteratorValue(const DictionaryIterator *self, FString *result) +{ + *result = self->Pair ? self->Pair->Value : FString {}; +} + +//===================================================================================== +// +// DictionaryIterator exports +// +//===================================================================================== + +DEFINE_ACTION_FUNCTION_NATIVE(_DictionaryIterator, Create, DictIteratorCreate) +{ + PARAM_PROLOGUE; + PARAM_POINTER(dict, Dictionary); + ACTION_RETURN_POINTER(DictIteratorCreate(dict)); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_DictionaryIterator, Next, DictIteratorNext) +{ + PARAM_SELF_STRUCT_PROLOGUE(DictionaryIterator); + ACTION_RETURN_BOOL(DictIteratorNext(self)); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_DictionaryIterator, Key, DictIteratorKey) +{ + PARAM_SELF_STRUCT_PROLOGUE(DictionaryIterator); + FString result; + DictIteratorKey(self, &result); + ACTION_RETURN_STRING(result); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_DictionaryIterator, Value, DictIteratorValue) +{ + PARAM_SELF_STRUCT_PROLOGUE(DictionaryIterator); + FString result; + DictIteratorValue(self, &result); + ACTION_RETURN_STRING(result); +} diff --git a/source/common/scripting/core/dictionary.h b/source/common/scripting/core/dictionary.h new file mode 100644 index 000000000..ef6bd3f68 --- /dev/null +++ b/source/common/scripting/core/dictionary.h @@ -0,0 +1,70 @@ +#pragma once + +#include "tarray.h" +#include "zstring.h" +#include "dobject.h" + +#include + +/** + * @brief The Dictionary class exists to be exported to ZScript. + * + * It is a string-to-string map. + * + * It is derived from DObject to be a part of normal GC process. + */ +class Dictionary final : public DObject +{ + DECLARE_CLASS(Dictionary, DObject) + +public: + + using StringMap = TMap; + using ConstIterator = StringMap::ConstIterator; + using ConstPair = StringMap::ConstPair; + + void Serialize(FSerializer &arc) override; + + StringMap Map; +}; + +/** + * @brief The DictionaryIterator class exists to be exported to ZScript. + * + * It provides iterating over a Dictionary. The order is not specified. + * + * It is derived from DObject to be a part of normal GC process. + */ +class DictionaryIterator final : public DObject +{ + DECLARE_CLASS(DictionaryIterator, DObject) + HAS_OBJECT_POINTERS + +public: + + ~DictionaryIterator() override = default; + + /** + * IMPLEMENT_CLASS macro needs a constructor without parameters. + * + * @see init(). + */ + DictionaryIterator(); + + void Serialize(FSerializer &arc) override; + + /** + * @brief init function complements constructor. + * @attention always call init after constructing DictionaryIterator. + */ + void init(Dictionary *dict); + + std::unique_ptr Iterator; + Dictionary::ConstPair *Pair; + + /** + * @brief Dictionary attribute exists for holding a pointer to iterated + * dictionary, so it is known by GC. + */ + Dictionary *Dict; +}; diff --git a/source/common/scripting/core/types.cpp b/source/common/scripting/core/types.cpp index d06c08250..447af4601 100644 --- a/source/common/scripting/core/types.cpp +++ b/source/common/scripting/core/types.cpp @@ -1078,6 +1078,46 @@ bool PName::ReadValue(FSerializer &ar, const char *key, void *addr) const } } +/* PStatePointer **********************************************************/ + +//========================================================================== +// +// PStatePointer Default Constructor +// +//========================================================================== + +PStatePointer::PStatePointer() +{ + mDescriptiveName = "Pointer"; + PointedType = NewStruct(NAME_State, nullptr, true); + IsConst = true; +} + +//========================================================================== +// +// PStatePointer :: WriteValue +// +//========================================================================== + +void PStatePointer::WriteValue(FSerializer& ar, const char* key, const void* addr) const +{ + ar.StatePointer(key, const_cast(addr), nullptr); +} + +//========================================================================== +// +// PStatePointer :: ReadValue +// +//========================================================================== + +bool PStatePointer::ReadValue(FSerializer& ar, const char* key, void* addr) const +{ + bool res = false; + ar.StatePointer(key, addr, &res); + return res; +} + + /* PSpriteID ******************************************************************/ //========================================================================== @@ -1102,9 +1142,7 @@ PSpriteID::PSpriteID() void PSpriteID::WriteValue(FSerializer &ar, const char *key, const void *addr) const { int32_t val = *(int*)addr; -#ifdef GZDOOM ar.Sprite(key, val, nullptr); -#endif } //========================================================================== @@ -1116,9 +1154,7 @@ void PSpriteID::WriteValue(FSerializer &ar, const char *key, const void *addr) c bool PSpriteID::ReadValue(FSerializer &ar, const char *key, void *addr) const { int32_t val = 0; -#ifdef GZDOOM ar.Sprite(key, val, nullptr); -#endif *(int*)addr = val; return true; } @@ -1440,49 +1476,6 @@ PPointer *NewPointer(PClass *cls, bool isconst) return static_cast(ptype); } -/* PStatePointer **********************************************************/ - -//========================================================================== -// -// PStatePointer Default Constructor -// -//========================================================================== - -PStatePointer::PStatePointer() -{ - mDescriptiveName = "Pointer"; - PointedType = NewStruct(NAME_State, nullptr, true); - IsConst = true; -} - -//========================================================================== -// -// PStatePointer :: WriteValue -// -//========================================================================== - -void PStatePointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const -{ -#ifdef GZDOOM - ar(key, *(FState **)addr); -#endif -} - -//========================================================================== -// -// PStatePointer :: ReadValue -// -//========================================================================== - -bool PStatePointer::ReadValue(FSerializer &ar, const char *key, void *addr) const -{ - bool res = false; -#ifdef GZDOOM - ::Serialize(ar, key, *(FState **)addr, nullptr, &res); -#endif - return res; -} - /* PClassPointer **********************************************************/