diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a213af8f73..2d71170eef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1181,7 +1181,6 @@ set (PCH_SOURCES dthinker.cpp edata.cpp f_wipe.cpp - farchive.cpp files.cpp g_doomedmap.cpp g_game.cpp @@ -1260,8 +1259,9 @@ set (PCH_SOURCES p_xlat.cpp parsecontext.cpp po_man.cpp - portal.cpp + portal.cpp r_utility.cpp + serializer.cpp sc_man.cpp st_stuff.cpp statistics.cpp @@ -1363,7 +1363,6 @@ set (PCH_SOURCES thingdef/thingdef_properties.cpp thingdef/thingdef_states.cpp xlat/parse_xlat.cpp - fragglescript/t_fspic.cpp fragglescript/t_func.cpp fragglescript/t_load.cpp fragglescript/t_oper.cpp @@ -1567,6 +1566,7 @@ source_group("Games\\Strife Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DI source_group("Intermission" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/intermission/.+") source_group("Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/math/.+") source_group("Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/menu/.+") +source_group("RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/rapidjson/.+") source_group("OpenGL Renderer" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/.+") source_group("OpenGL Renderer\\Data" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/data/.+") source_group("OpenGL Renderer\\Dynamic Lights" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/gl/dynlights/.+") diff --git a/src/actor.h b/src/actor.h index 1937419954..bf76cc15ea 100644 --- a/src/actor.h +++ b/src/actor.h @@ -583,8 +583,9 @@ public: void Destroy (); ~AActor (); - void Serialize (FArchive &arc); - + void Serialize(FSerializer &arc); + void PostSerialize(); + static AActor *StaticSpawn (PClassActor *type, const DVector3 &pos, replace_t allowreplacement, bool SpawningMapThing = false); inline AActor *GetDefault () const @@ -988,7 +989,7 @@ public: double Speed; double FloatSpeed; - WORD sprite; // used to find patch_t and flip value + int sprite; // used to find patch_t and flip value BYTE frame; // sprite frame to draw DVector2 Scale; // Scaling values; 1 is normal size FRenderStyle RenderStyle; // Style to draw this actor with @@ -1183,8 +1184,9 @@ public: private: static AActor *TIDHash[128]; static inline int TIDHASH (int key) { return key & 127; } +public: static FSharedStringArena mStringPropertyData; - +private: friend class FActorIterator; friend bool P_IsTIDUsed(int tid); diff --git a/src/am_map.cpp b/src/am_map.cpp index 7e346843b0..9292e81d9f 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -38,7 +38,7 @@ #include "gi.h" #include "p_setup.h" #include "c_bind.h" -#include "farchive.h" +#include "serializer.h" #include "r_renderer.h" #include "r_sky.h" #include "sbar.h" @@ -3106,13 +3106,14 @@ void AM_Drawer () // //============================================================================= -void AM_SerializeMarkers(FArchive &arc) +void AM_SerializeMarkers(FSerializer &arc) { - arc << markpointnum; - for (int i=0; i %d bots\n", count); } -FArchive &operator<< (FArchive &arc, botskill_t &skill) -{ - return arc << skill.aiming << skill.perfection << skill.reaction << skill.isp; -} - // set the bot specific weapon information // This is intentionally not in the weapon definition anymore. void InitBotStuff() diff --git a/src/b_bot.h b/src/b_bot.h index f9085d6fb4..5dfbb7e63a 100644 --- a/src/b_bot.h +++ b/src/b_bot.h @@ -58,8 +58,6 @@ struct botskill_t int isp; //Instincts of Self Preservation. Personality }; -FArchive &operator<< (FArchive &arc, botskill_t &skill); - enum { BOTINUSE_No, @@ -142,7 +140,7 @@ public: DBot (); void Clear (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); //(b_think.cpp) diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 03a7495e4e..f9cea4005b 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -811,7 +811,7 @@ CCMD (load) return; } FString fname = argv[1]; - DefaultExtension (fname, ".zds"); + DefaultExtension (fname, "." SAVEGAME_EXT); G_LoadGame (fname); } @@ -831,7 +831,7 @@ CCMD (save) return; } FString fname = argv[1]; - DefaultExtension (fname, ".zds"); + DefaultExtension (fname, "." SAVEGAME_EXT); G_SaveGame (fname, argv.argc() > 2 ? argv[2] : argv[1]); } diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 811052e1c2..c3fc9bae11 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -53,7 +53,7 @@ #include "v_text.h" #include "d_net.h" #include "d_main.h" -#include "farchive.h" +#include "serializer.h" // MACROS ------------------------------------------------------------------ @@ -65,7 +65,7 @@ class DWaitingCommand : public DThinker public: DWaitingCommand (const char *cmd, int tics); ~DWaitingCommand (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); private: @@ -189,10 +189,11 @@ static const char *KeyConfCommands[] = IMPLEMENT_CLASS (DWaitingCommand) -void DWaitingCommand::Serialize (FArchive &arc) +void DWaitingCommand::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Command << TicsLeft; + arc("command", Command) + ("ticsleft", TicsLeft); } DWaitingCommand::DWaitingCommand () diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index dfa4f56f0a..9e068b5968 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -69,7 +69,7 @@ #include "i_system.h" #include "doomerrors.h" #include "p_effect.h" -#include "farchive.h" +#include "serializer.h" #include "vmbuilder.h" // [SO] Just the way Randy said to do it :) @@ -3198,8 +3198,8 @@ PClassActor *ADehackedPickup::DetermineType () return NULL; } -void ADehackedPickup::Serialize(FArchive &arc) +void ADehackedPickup::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << droppedbymonster; + arc("droppedbymonster", droppedbymonster); } diff --git a/src/d_dehacked.h b/src/d_dehacked.h index 4418f4f097..564a6d499b 100644 --- a/src/d_dehacked.h +++ b/src/d_dehacked.h @@ -48,7 +48,8 @@ public: bool TryPickup (AActor *&toucher); void PlayPickupSound (AActor *toucher); void DoPickupSpecial (AActor *toucher); - void Serialize(FArchive &arc); + + void Serialize(FSerializer &arc); private: PClassActor *DetermineType (); AInventory *RealPickup; diff --git a/src/d_main.cpp b/src/d_main.cpp index 9f4568614a..2dbf1273c3 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2580,7 +2580,7 @@ void D_DoomMain (void) { FString file(v); FixPathSeperator (file); - DefaultExtension (file, ".zds"); + DefaultExtension (file, "." SAVEGAME_EXT); G_LoadGame (file); } diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index 3504c58744..d3ce8c9437 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -56,7 +56,7 @@ #include "r_data/r_translate.h" #include "templates.h" #include "cmdlib.h" -#include "farchive.h" +#include "serializer.h" static FRandom pr_pickteam ("PickRandomTeam"); @@ -880,69 +880,75 @@ void D_ReadUserInfoStrings (int pnum, BYTE **stream, bool update) *stream += strlen (*((char **)stream)) + 1; } -void WriteUserInfo(FArchive &arc, userinfo_t &info) +void WriteUserInfo(FSerializer &arc, userinfo_t &info) { - TMapIterator it(info); - TMap::Pair *pair; - FName name; - UCVarValue val; - int i; - - while (it.NextPair(pair)) + if (arc.BeginObject("userinfo")) { - name = pair->Key; - arc << name; - switch (name.GetIndex()) + TMapIterator it(info); + TMap::Pair *pair; + FString name; + const char *string; + UCVarValue val; + int i; + + while (it.NextPair(pair)) { - case NAME_Skin: - arc.WriteString(skins[info.GetSkin()].name); - break; + name = pair->Key; + name.ToLower(); + switch (pair->Key.GetIndex()) + { + case NAME_Skin: + string = skins[info.GetSkin()].name; + break; - case NAME_PlayerClass: - i = info.GetPlayerClassNum(); - arc.WriteString(i == -1 ? "Random" : PlayerClasses[i].Type->DisplayName.GetChars()); - break; + case NAME_PlayerClass: + i = info.GetPlayerClassNum(); + string = (i == -1 ? "Random" : PlayerClasses[i].Type->DisplayName.GetChars()); + break; - default: - val = pair->Value->GetGenericRep(CVAR_String); - arc.WriteString(val.String); - break; + default: + val = pair->Value->GetGenericRep(CVAR_String); + string = val.String; + break; + } + arc.StringPtr(name, string); } + arc.EndObject(); } - name = NAME_None; - arc << name; } -void ReadUserInfo(FArchive &arc, userinfo_t &info, FString &skin) +void ReadUserInfo(FSerializer &arc, userinfo_t &info, FString &skin) { FName name; FBaseCVar **cvar; - char *str = NULL; UCVarValue val; + const char *key; + const char *str; info.Reset(); skin = NULL; - for (arc << name; name != NAME_None; arc << name) + if (arc.BeginObject("userinfo")) { - cvar = info.CheckKey(name); - arc << str; - if (cvar != NULL && *cvar != NULL) + while ((key = arc.GetKey())) { - switch (name) + arc.StringPtr(nullptr, str); + name = key; + cvar = info.CheckKey(name); + if (cvar != NULL && *cvar != NULL) { - case NAME_Team: info.TeamChanged(atoi(str)); break; - case NAME_Skin: skin = str; break; // Caller must call SkinChanged() once current calss is known - case NAME_PlayerClass: info.PlayerClassChanged(str); break; - default: - val.String = str; - (*cvar)->SetGenericRep(val, CVAR_String); - break; + switch (name) + { + case NAME_Team: info.TeamChanged(atoi(str)); break; + case NAME_Skin: skin = str; break; // Caller must call SkinChanged() once current calss is known + case NAME_PlayerClass: info.PlayerClassChanged(str); break; + default: + val.String = str; + (*cvar)->SetGenericRep(val, CVAR_String); + break; + } } } - } - if (str != NULL) - { - delete[] str; + arc.EndObject(); } } diff --git a/src/d_player.h b/src/d_player.h index 5206fd7708..e944d67348 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -102,7 +102,8 @@ class APlayerPawn : public AActor DECLARE_CLASS_WITH_META(APlayerPawn, AActor, PClassPlayerPawn) HAS_OBJECT_POINTERS public: - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual void PostBeginPlay(); virtual void Tick(); @@ -374,8 +375,8 @@ struct userinfo_t : TMap int ColorSetChanged(int setnum); }; -void ReadUserInfo(FArchive &arc, userinfo_t &info, FString &skin); -void WriteUserInfo(FArchive &arc, userinfo_t &info); +void ReadUserInfo(FSerializer &arc, userinfo_t &info, FString &skin); +void WriteUserInfo(FSerializer &arc, userinfo_t &info); // // Extended player object info: player_t @@ -387,7 +388,7 @@ public: ~player_t(); player_t &operator= (const player_t &p); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); size_t FixPointers (const DObject *obj, DObject *replacement); size_t PropagateMark(); @@ -545,8 +546,6 @@ public: // Bookkeeping on players - state. extern player_t players[MAXPLAYERS]; -FArchive &operator<< (FArchive &arc, player_t *&p); - void P_CheckPlayerSprite(AActor *mo, int &spritenum, DVector2 &scale); inline void AActor::SetFriendPlayer(player_t *player) diff --git a/src/d_protocol.cpp b/src/d_protocol.cpp index df6f9f23ac..6d7200f8c1 100644 --- a/src/d_protocol.cpp +++ b/src/d_protocol.cpp @@ -38,7 +38,7 @@ #include "doomdef.h" #include "doomstat.h" #include "cmdlib.h" -#include "farchive.h" +#include "serializer.h" char *ReadString (BYTE **stream) @@ -287,27 +287,33 @@ int PackUserCmd (const usercmd_t *ucmd, const usercmd_t *basis, BYTE **stream) return int(*stream - start); } -FArchive &operator<< (FArchive &arc, ticcmd_t &cmd) +FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &cmd, ticcmd_t *def) { - return arc << cmd.consistancy << cmd.ucmd; + if (arc.BeginObject(key)) + { + arc("consistency", cmd.consistancy) + ("ucmd", cmd.ucmd) + .EndObject(); + } + return arc; } -FArchive &operator<< (FArchive &arc, usercmd_t &cmd) +FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercmd_t *def) { - BYTE bytes[256]; - BYTE *stream = bytes; - if (arc.IsStoring ()) + // This used packed data with the old serializer but that's totally counterproductive when + // having a text format that is human-readable. So this compression has been undone here. + // The few bytes of file size it saves are not worth the obfuscation. + + if (arc.BeginObject(key)) { - BYTE len = PackUserCmd (&cmd, NULL, &stream); - arc << len; - arc.Write (bytes, len); - } - else - { - BYTE len; - arc << len; - arc.Read (bytes, len); - UnpackUserCmd (&cmd, NULL, &stream); + arc("buttons", cmd.buttons) + ("pitch", cmd.pitch) + ("yaw", cmd.yaw) + ("roll", cmd.roll) + ("forwardmove", cmd.forwardmove) + ("sidemove", cmd.sidemove) + ("upmove", cmd.upmove) + .EndObject(); } return arc; } diff --git a/src/d_protocol.h b/src/d_protocol.h index 61f7377e3b..2813b14dc5 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -72,10 +72,6 @@ struct usercmd_t short upmove; }; -class FArchive; - -FArchive &operator<< (FArchive &arc, usercmd_t &cmd); - // When transmitted, the above message is preceded by a byte // indicating which fields are actually present in the message. enum diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 5142a3c4ee..7e819629c2 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -35,7 +35,4 @@ struct ticcmd_t SWORD consistancy; // checks for net game }; - -FArchive &operator<< (FArchive &arc, ticcmd_t &cmd); - #endif // __D_TICCMD_H__ diff --git a/src/decallib.cpp b/src/decallib.cpp index fe8de4d0a4..fc7e465dc6 100644 --- a/src/decallib.cpp +++ b/src/decallib.cpp @@ -48,7 +48,7 @@ #include "g_level.h" #include "colormatcher.h" #include "b_bot.h" -#include "farchive.h" +#include "serializer.h" FDecalLib DecalLibrary; @@ -113,7 +113,7 @@ struct DDecalThinker : public DThinker HAS_OBJECT_POINTERS public: DDecalThinker (DBaseDecal *decal) : DThinker (STAT_DECALTHINKER), TheDecal (decal) {} - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); TObjPtr TheDecal; protected: DDecalThinker () : DThinker (STAT_DECALTHINKER) {} @@ -123,10 +123,10 @@ IMPLEMENT_POINTY_CLASS (DDecalThinker) DECLARE_POINTER (TheDecal) END_POINTERS -void DDecalThinker::Serialize (FArchive &arc) +void DDecalThinker::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TheDecal; + arc("thedecal", TheDecal); } struct FDecalFaderAnim : public FDecalAnimator @@ -143,7 +143,7 @@ class DDecalFader : public DDecalThinker DECLARE_CLASS (DDecalFader, DDecalThinker) public: DDecalFader (DBaseDecal *decal) : DDecalThinker (decal) {} - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); int TimeToStartDecay; @@ -168,7 +168,7 @@ class DDecalColorer : public DDecalThinker DECLARE_CLASS (DDecalColorer, DDecalThinker) public: DDecalColorer (DBaseDecal *decal) : DDecalThinker (decal) {} - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); int TimeToStartDecay; @@ -194,7 +194,7 @@ class DDecalStretcher : public DDecalThinker DECLARE_CLASS (DDecalStretcher, DDecalThinker) public: DDecalStretcher (DBaseDecal *decal) : DDecalThinker (decal) {} - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); int TimeToStart; @@ -225,7 +225,7 @@ class DDecalSlider : public DDecalThinker DECLARE_CLASS (DDecalSlider, DDecalThinker) public: DDecalSlider (DBaseDecal *decal) : DDecalThinker (decal) {} - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); int TimeToStart; @@ -1153,10 +1153,12 @@ FDecalAnimator::~FDecalAnimator () IMPLEMENT_CLASS (DDecalFader) -void DDecalFader::Serialize (FArchive &arc) +void DDecalFader::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TimeToStartDecay << TimeToEndDecay << StartTrans; + arc("starttime", TimeToStartDecay) + ("endtime", TimeToEndDecay) + ("starttrans", StartTrans); } void DDecalFader::Tick () @@ -1200,18 +1202,18 @@ DThinker *FDecalFaderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const IMPLEMENT_CLASS (DDecalStretcher) -void DDecalStretcher::Serialize (FArchive &arc) +void DDecalStretcher::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TimeToStart - << TimeToStop - << GoalX - << StartX - << bStretchX - << GoalY - << StartY - << bStretchY - << bStarted; + arc("starttime", TimeToStart) + ("endtime", TimeToStop) + ("goalx", GoalX) + ("startx", StartX) + ("stretchx", bStretchX) + ("goaly", GoalY) + ("starty", StartY) + ("stretchy", bStretchY) + ("started", bStarted); } DThinker *FDecalStretcherAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const @@ -1288,16 +1290,14 @@ void DDecalStretcher::Tick () IMPLEMENT_CLASS (DDecalSlider) -void DDecalSlider::Serialize (FArchive &arc) +void DDecalSlider::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TimeToStart - << TimeToStop - /*<< DistX*/ - << DistY - /*<< StartX*/ - << StartY - << bStarted; + arc("starttime", TimeToStart) + ("endtime", TimeToStop) + ("disty", DistY) + ("starty", StartY) + ("started", bStarted); } DThinker *FDecalSliderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const @@ -1371,10 +1371,13 @@ FDecalAnimator *FDecalLib::FindAnimator (const char *name) IMPLEMENT_CLASS (DDecalColorer) -void DDecalColorer::Serialize (FArchive &arc) +void DDecalColorer::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TimeToStartDecay << TimeToEndDecay << StartColor << GoalColor; + arc("starttime", TimeToStartDecay) + ("endtime", TimeToEndDecay) + ("startcolor", StartColor) + ("goalcolor", GoalColor); } void DDecalColorer::Tick () diff --git a/src/dobject.cpp b/src/dobject.cpp index 82014c133c..8fba75aa6b 100644 --- a/src/dobject.cpp +++ b/src/dobject.cpp @@ -47,7 +47,13 @@ #include "stats.h" #include "a_sharedglobal.h" #include "dsectoreffect.h" -#include "farchive.h" +#include "serializer.h" + +//========================================================================== +// +// +// +//========================================================================== ClassReg DObject::RegistrationInfo = { @@ -61,6 +67,12 @@ ClassReg DObject::RegistrationInfo = }; _DECLARE_TI(DObject) +//========================================================================== +// +// +// +//========================================================================== + CCMD (dumpactors) { const char *const filters[32] = @@ -95,6 +107,12 @@ CCMD (dumpactors) } } +//========================================================================== +// +// +// +//========================================================================== + CCMD (dumpclasses) { // This is by no means speed-optimized. But it's an informational console @@ -234,6 +252,12 @@ CCMD (dumpclasses) Printf ("%d classes shown, %d omitted\n", shown, omitted); } +//========================================================================== +// +// +// +//========================================================================== + void DObject::InPlaceConstructor (void *mem) { new ((EInPlace *)mem) DObject; @@ -255,6 +279,12 @@ DObject::DObject (PClass *inClass) GC::Root = this; } +//========================================================================== +// +// +// +//========================================================================== + DObject::~DObject () { if (!PClass::bShutdown) @@ -304,11 +334,23 @@ DObject::~DObject () } } +//========================================================================== +// +// +// +//========================================================================== + void DObject::Destroy () { ObjectFlags = (ObjectFlags & ~OF_Fixed) | OF_EuthanizeMe; } +//========================================================================== +// +// +// +//========================================================================== + size_t DObject::PropagateMark() { const PClass *info = GetClass(); @@ -330,6 +372,12 @@ size_t DObject::PropagateMark() return 0; } +//========================================================================== +// +// +// +//========================================================================== + size_t DObject::PointerSubstitution (DObject *old, DObject *notOld) { const PClass *info = GetClass(); @@ -352,6 +400,12 @@ size_t DObject::PointerSubstitution (DObject *old, DObject *notOld) return changed; } +//========================================================================== +// +// +// +//========================================================================== + size_t DObject::StaticPointerSubstitution (DObject *old, DObject *notOld) { DObject *probe; @@ -417,70 +471,41 @@ size_t DObject::StaticPointerSubstitution (DObject *old, DObject *notOld) return changed; } -void DObject::SerializeUserVars(FArchive &arc) +//========================================================================== +// +// +// +//========================================================================== + +void DObject::SerializeUserVars(FSerializer &arc) { - PSymbolTable *symt; - FName varname; - DWORD count, j; - int *varloc = NULL; - - symt = &GetClass()->Symbols; - - if (arc.IsStoring()) + if (arc.isWriting()) { // Write all fields that aren't serialized by native code. - GetClass()->WriteValue(arc, this); - } - else if (SaveVersion >= 4535) - { - GetClass()->ReadValue(arc, this); + GetClass()->WriteAllFields(arc, this); } else - { // Old version that only deals with ints - // Read user variables until 'None' is encountered. - arc << varname; - while (varname != NAME_None) - { - PField *var = dyn_cast(symt->FindSymbol(varname, true)); - DWORD wanted = 0; - - if (var != NULL && !(var->Flags & VARF_Native)) - { - PType *type = var->Type; - PArray *arraytype = dyn_cast(type); - if (arraytype != NULL) - { - wanted = arraytype->ElementCount; - type = arraytype->ElementType; - } - else - { - wanted = 1; - } - assert(type == TypeSInt32); - varloc = (int *)(reinterpret_cast(this) + var->Offset); - } - count = arc.ReadCount(); - for (j = 0; j < MIN(wanted, count); ++j) - { - arc << varloc[j]; - } - if (wanted < count) - { - // Ignore remaining values from archive. - for (; j < count; ++j) - { - int foo; - arc << foo; - } - } - arc << varname; - } + { + GetClass()->ReadAllFields(arc, this); } } -void DObject::Serialize (FArchive &arc) + +//========================================================================== +// +// +// +//========================================================================== + +void DObject::Serialize(FSerializer &arc) { + int fresh = ObjectFlags & OF_JustSpawned; + int freshdef = 0; + arc("justspawned", fresh, freshdef); + if (arc.isReading()) + { + ObjectFlags |= fresh; + } ObjectFlags |= OF_SerialSuccess; } diff --git a/src/dobject.h b/src/dobject.h index 682e9bb082..51102bc4c2 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -39,7 +39,7 @@ class PClass; -class FArchive; +class FSerializer; class DObject; /* @@ -413,16 +413,12 @@ public: return GC::ReadBarrier(p) == u; } - template friend inline FArchive &operator<<(FArchive &arc, TObjPtr &o); template friend inline void GC::Mark(TObjPtr &obj); + template friend FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, TObjPtr *); + friend class DObject; }; -template inline FArchive &operator<<(FArchive &arc, TObjPtr &o) -{ - return arc << o.p; -} - // Use barrier_cast instead of static_cast when you need to cast // the contents of a TObjPtr to a related type. template inline T barrier_cast(TObjPtr &o) @@ -463,8 +459,9 @@ public: inline bool IsKindOf (const PClass *base) const; inline bool IsA (const PClass *type) const; - void SerializeUserVars(FArchive &arc); - virtual void Serialize (FArchive &arc); + void SerializeUserVars(FSerializer &arc); + virtual void Serialize(FSerializer &arc); + void ClearClass() { Class = NULL; diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 7a593c5fe3..9693da1375 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -39,7 +39,7 @@ #include "dobject.h" #include "i_system.h" -#include "farchive.h" +#include "serializer.h" #include "actor.h" #include "templates.h" #include "autosegs.h" @@ -47,6 +47,7 @@ #include "a_pickups.h" #include "a_weaponpiece.h" #include "d_player.h" +#include "doomerrors.h" #include "fragglescript/t_fs.h" // MACROS ------------------------------------------------------------------ @@ -390,7 +391,7 @@ bool PType::VisitedNodeSet::Check(const PType *node) // //========================================================================== -void PType::WriteValue(FArchive &ar, const void *addr) const +void PType::WriteValue(FSerializer &ar, const char *key,const void *addr) const { assert(0 && "Cannot write value for this type"); } @@ -401,104 +402,12 @@ void PType::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PType::ReadValue(FArchive &ar, void *addr) const +bool PType::ReadValue(FSerializer &ar, const char *key, void *addr) const { assert(0 && "Cannot read value for this type"); - SkipValue(ar); return false; } -//========================================================================== -// -// PType :: SkipValue STATIC -// -//========================================================================== - -void PType::SkipValue(FArchive &ar) -{ - BYTE tag; - ar << tag; - SkipValue(ar, tag); -} - -void PType::SkipValue(FArchive &ar, int tag) -{ - assert(ar.IsLoading() && "SkipValue passed an archive that is writing"); - BYTE buff[8]; - - switch (tag) - { - case VAL_Zero: case VAL_One: - break; - - case VAL_Int8: case VAL_UInt8: - ar.Read(buff, 1); - break; - - case VAL_Int16: case VAL_UInt16: - ar.Read(buff, 2); - break; - - case VAL_Int32: case VAL_UInt32: case VAL_Float32: - ar.Read(buff, 4); - break; - - case VAL_Int64: case VAL_UInt64: case VAL_Float64: - ar.Read(buff, 8); - break; - - case VAL_Name: - ar.ReadName(); - break; - - case VAL_Object: - { - DObject *skipper; - ar << skipper; - break; - } - case VAL_State: - { - FState *skipper; - ar << skipper; - break; - } - case VAL_String: - { - FString skipper; - ar << skipper; - break; - } - case VAL_Array: - { - DWORD count = ar.ReadCount(); - while (count-- > 0) - { - SkipValue(ar); - } - break; - } - case VAL_Struct: - { - const char *label; - for (label = ar.ReadName(); label != NULL; label = ar.ReadName()) - { - SkipValue(ar); - } - break; - } - case VAL_Class: - { - PClass *type; - for (ar.UserReadClass(type); type != NULL; ar.UserReadClass(type)) - { - SkipValue(ar, VAL_Struct); - } - break; - } - } -} - //========================================================================== // // PType :: SetDefaultValue @@ -805,116 +714,41 @@ PInt::PInt(unsigned int size, bool unsign) // // PInt :: WriteValue // -// Write the value using the minimum byte size needed to represent it. This -// means that the value as written is not necessarily of the same type as -// stored, but the signedness information is preserved. -// //========================================================================== -void PInt::WriteValue(FArchive &ar, const void *addr) const +void PInt::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - BYTE bval; - - // The process for bytes is the same whether signed or unsigned, since - // they can't be compacted into a representation with fewer bytes. - if (Size == 1) + if (Size == 8 && Unsigned) { - bval = *(BYTE *)addr; - } - else if (Unsigned) - { - unsigned val; - if (Size == 8) - { - QWORD qval = *(QWORD *)addr; - if (qval & 0xFFFFFFFF00000000llu) - { // Value needs 64 bits - ar.WriteByte(VAL_UInt64); - ar.WriteInt64(qval); - return; - } - // Value can fit in 32 bits or less - val = (unsigned)qval; - goto check_u32; - } - else if (Size == 4) - { - val = *(DWORD *)addr; -check_u32: if (val & 0xFFFF0000u) - { // Value needs 32 bits - ar.WriteByte(VAL_UInt32); - ar.WriteInt32(val); - return; - } - // Value can fit in 16 bits or less - goto check_u16; - } - else// if (Size == 2) - { - val = *(WORD *)addr; -check_u16: if (val & 0xFFFFFF00u) - { // Value needs 16 bits - ar.WriteByte(VAL_UInt16); - ar.WriteInt16(val); - return; - } - // Value can fit in 8 bits - bval = (BYTE)val; - } - } - else // Signed - { - int val; - if (Size == 8) - { - SQWORD qval = *(SQWORD *)addr; - INT_MIN; - if (qval < (-0x7FFFFFFF - 1) || qval > 0x7FFFFFFF) - { // Value needs 64 bits - ar.WriteByte(VAL_Int64); - ar.WriteInt64(qval); - return; - } - // Value can fit in 32 bits or less - val = (int)qval; - goto check_s32; - } - else if (Size == 4) - { - val = *(SDWORD *)addr; -check_s32: if (val < -0x8000 || val > 0x7FFF) - { // Value needs 32 bits - ar.WriteByte(VAL_Int32); - ar.WriteInt32(val); - return; - } - // Value can fit in 16 bits or less - goto check_s16; - } - else// if (Size == 2) - { - val = *(SWORD *)addr; -check_s16: if (val < -0x80 || val > 0x7F) - { // Value needs 16 bits - ar.WriteByte(VAL_Int16); - ar.WriteInt16(val); - return; - } - // Value can fit in 8 bits - bval = (BYTE)val; - } - } - // If we get here, the value fits in a byte. Values of 0 and 1 are - // optimized away into the tag so they don't require any extra space - // to store. - if (bval & 0xFE) - { - BYTE out[2] = { Unsigned ? VAL_UInt8 : VAL_Int8, bval }; - ar.Write(out, 2); + // this is a special case that cannot be represented by an int64_t. + uint64_t val = *(uint64_t*)addr; + ar(key, val); } else { - ar.WriteByte(VAL_Zero + bval); + int64_t val; + switch (Size) + { + case 1: + val = Unsigned ? *(uint8_t*)addr : *(int8_t*)addr; + break; + + case 2: + val = Unsigned ? *(uint16_t*)addr : *(int16_t*)addr; + break; + + case 4: + val = Unsigned ? *(uint32_t*)addr : *(int32_t*)addr; + break; + + case 8: + val = *(int64_t*)addr; + break; + + default: + return; // something invalid + } + ar(key, val); } } @@ -924,47 +758,37 @@ check_s16: if (val < -0x80 || val > 0x7F) // //========================================================================== -bool PInt::ReadValue(FArchive &ar, void *addr) const +bool PInt::ReadValue(FSerializer &ar, const char *key, void *addr) const { - union - { - QWORD uval; - SQWORD sval; - }; - BYTE tag; - union - { - BYTE val8; - WORD val16; - DWORD val32; - float single; - double dbl; - }; + NumericValue val; - ar << tag; - switch (tag) - { - case VAL_Zero: uval = 0; break; - case VAL_One: uval = 1; break; - case VAL_Int8: ar << val8; sval = (SBYTE)val8; break; - case VAL_UInt8: ar << val8; uval = val8; break; - case VAL_Int16: ar << val16; sval = (SWORD)val16; break; - case VAL_UInt16: ar << val16; uval = val16; break; - case VAL_Int32: ar << val32; sval = (SDWORD)val32; break; - case VAL_UInt32: ar << val32; uval = val32; break; - case VAL_Int64: ar << sval; break; - case VAL_UInt64: ar << uval; break; - case VAL_Float32: ar << single; sval = (SQWORD)single; break; - case VAL_Float64: ar << dbl; sval = (SQWORD)dbl; break; - default: SkipValue(ar, tag); return false; // Incompatible type - } + ar(key, val); + if (val.type == NumericValue::NM_invalid) return false; // not found or usable + if (val.type == NumericValue::NM_float) val.signedval = (int64_t)val.floatval; + + // No need to check the unsigned state here. Downcasting to smaller types will yield the same result for both. switch (Size) { - case 1: *(BYTE *)addr = (BYTE)uval; break; - case 2: *(WORD *)addr = (WORD)uval; break; - case 4: *(DWORD *)addr = (DWORD)uval; break; - case 8: *(QWORD *)addr = uval; break; + case 1: + *(uint8_t*)addr = (uint8_t)val.signedval; + break; + + case 2: + *(uint16_t*)addr = (uint16_t)val.signedval; + break; + + case 4: + *(uint32_t*)addr = (uint32_t)val.signedval; + break; + + case 8: + *(uint64_t*)addr = (uint64_t)val.signedval; + break; + + default: + return false; // something invalid } + return true; } @@ -1259,28 +1083,16 @@ void PFloat::SetSymbols(const PFloat::SymbolInitI *sym, size_t count) // //========================================================================== -void PFloat::WriteValue(FArchive &ar, const void *addr) const +void PFloat::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - float singleprecision; if (Size == 8) { - // If it can be written as single precision without information - // loss, then prefer that over writing a full-sized double. - double doubleprecision = *(double *)addr; - singleprecision = (float)doubleprecision; - if (singleprecision != doubleprecision) - { - ar.WriteByte(VAL_Float64); - ar << doubleprecision; - return; - } + ar(key, *(double*)addr); } else { - singleprecision = *(float *)addr; + ar(key, *(float*)addr); } - ar.WriteByte(VAL_Float32); - ar << singleprecision; } //========================================================================== @@ -1289,60 +1101,26 @@ void PFloat::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -static bool ReadValueDbl(FArchive &ar, double *addr, unsigned tag) +bool PFloat::ReadValue(FSerializer &ar, const char *key, void *addr) const { - double val; - union - { - BYTE val8; - WORD val16; - DWORD val32; - QWORD val64; - fixed_t fix; - float single; - angle_t ang; - }; + NumericValue val; - switch (tag) + ar(key, val); + if (val.type == NumericValue::NM_invalid) return false; // not found or usable + else if (val.type == NumericValue::NM_signed) val.floatval = (double)val.signedval; + else if (val.type == NumericValue::NM_unsigned) val.floatval = (double)val.unsignedval; + + if (Size == 8) { - case VAL_Zero: val = 0; break; - case VAL_One: val = 1; break; - case VAL_Int8: ar << val8; val = (SBYTE)val8; break; - case VAL_UInt8: ar << val8; val = val8; break; - case VAL_Int16: ar << val16; val = (SWORD)val16; break; - case VAL_UInt16: ar << val16; val = val16; break; - case VAL_Int32: ar << val32; val = (SDWORD)val32; break; - case VAL_UInt32: ar << val32; val = val32; break; - case VAL_Int64: ar << val64; val = (double)(SQWORD)val64; break; - case VAL_UInt64: ar << val64; val = (double)val64; break; - case VAL_Float32: ar << single; val = single; break; - case VAL_Float64: ar << val; break; - default: PType::SkipValue(ar, tag); return false; // Incompatible type + *(double*)addr = val.floatval; + } + else + { + *(float*)addr = (float)val.floatval; } - *(double *)addr = val; return true; } -bool PFloat::ReadValue(FArchive &ar, void *addr) const -{ - BYTE tag; - ar << tag; - double val; - if (ReadValueDbl(ar, &val, tag)) - { - if (Size == 4) - { - *(float *)addr = (float)val; - } - else - { - *(double *)addr = val; - } - return true; - } - return false; -} - //========================================================================== // // PFloat :: SetValue @@ -1482,10 +1260,9 @@ int PString::GetRegType() const // //========================================================================== -void PString::WriteValue(FArchive &ar, const void *addr) const +void PString::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - ar.WriteByte(VAL_String); - ar.WriteString(*(const FString *)addr); + ar(key, *(FString*)addr); } //========================================================================== @@ -1494,25 +1271,19 @@ void PString::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PString::ReadValue(FArchive &ar, void *addr) const +bool PString::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - ar << tag; - if (tag == VAL_String) + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) { - ar << *(FString *)addr; - } - else if (tag == VAL_Name) - { - const char *str = ar.ReadName(); - *(FString *)addr = str; + return false; } else { - SkipValue(ar, tag); - return false; + *(FString*)addr = cptr; + return true; } - return true; } //========================================================================== @@ -1574,10 +1345,10 @@ PName::PName() // //========================================================================== -void PName::WriteValue(FArchive &ar, const void *addr) const +void PName::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - ar.WriteByte(VAL_Name); - ar.WriteName(((const FName *)addr)->GetChars()); + const char *cptr = ((const FName*)addr)->GetChars(); + ar.StringPtr(key, cptr); } //========================================================================== @@ -1586,26 +1357,19 @@ void PName::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PName::ReadValue(FArchive &ar, void *addr) const +bool PName::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - ar << tag; - if (tag == VAL_Name) + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) { - *(FName *)addr = FName(ar.ReadName()); - } - else if (tag == VAL_String) - { - FString str; - ar << str; - *(FName *)addr = FName(str); + return false; } else { - SkipValue(ar, tag); - return false; + *(FName*)addr = FName(cptr); + return true; } - return true; } /* PSound *****************************************************************/ @@ -1630,10 +1394,10 @@ PSound::PSound() // //========================================================================== -void PSound::WriteValue(FArchive &ar, const void *addr) const +void PSound::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - ar.WriteByte(VAL_Name); - ar.WriteName(*(const FSoundID *)addr); + const char *cptr = *(const FSoundID *)addr; + ar.StringPtr(key, cptr); } //========================================================================== @@ -1642,28 +1406,19 @@ void PSound::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PSound::ReadValue(FArchive &ar, void *addr) const +bool PSound::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - - ar << tag; - if (tag == VAL_Name) + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) { - const char *str = ar.ReadName(); - *(FSoundID *)addr = FSoundID(str); - } - else if (tag == VAL_String) - { - FString str; - ar << str; - *(FSoundID *)addr = FSoundID(str); + return false; } else { - SkipValue(ar, tag); - return false; + *(FSoundID *)addr = FSoundID(cptr); + return true; } - return true; } /* PColor *****************************************************************/ @@ -1736,10 +1491,9 @@ int PStatePointer::GetRegType() const // //========================================================================== -void PStatePointer::WriteValue(FArchive &ar, const void *addr) const +void PStatePointer::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - ar.WriteByte(VAL_State); - ar << *(FState **)addr; + ar(key, *(FState **)addr); } //========================================================================== @@ -1748,17 +1502,11 @@ void PStatePointer::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PStatePointer::ReadValue(FArchive &ar, void *addr) const +bool PStatePointer::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - ar << tag; - if (tag == VAL_State) - { - ar << *(FState **)addr; - return true; - } - SkipValue(ar, tag); - return false; + bool res = false; + ::Serialize(ar, key, *(FState **)addr, nullptr, &res); + return res; } /* PPointer ***************************************************************/ @@ -1854,12 +1602,11 @@ void PPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const // //========================================================================== -void PPointer::WriteValue(FArchive &ar, const void *addr) const +void PPointer::WriteValue(FSerializer &ar, const char *key,const void *addr) const { if (PointedType->IsKindOf(RUNTIME_CLASS(PClass))) { - ar.WriteByte(VAL_Object); - ar << *(DObject **)addr; + ar(key, *(DObject **)addr); } else { @@ -1874,16 +1621,14 @@ void PPointer::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PPointer::ReadValue(FArchive &ar, void *addr) const +bool PPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - ar << tag; - if (tag == VAL_Object && PointedType->IsKindOf(RUNTIME_CLASS(PClass))) + if (PointedType->IsKindOf(RUNTIME_CLASS(PClass))) { - ar << *(DObject **)addr; - return true; + bool res = false; + ::Serialize(ar, key, *(DObject **)addr, nullptr, &res); + return res; } - SkipValue(ar, tag); return false; } @@ -2098,15 +1843,17 @@ void PArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const // //========================================================================== -void PArray::WriteValue(FArchive &ar, const void *addr) const +void PArray::WriteValue(FSerializer &ar, const char *key,const void *addr) const { - ar.WriteByte(VAL_Array); - ar.WriteCount(ElementCount); - const BYTE *addrb = (const BYTE *)addr; - for (unsigned i = 0; i < ElementCount; ++i) + if (ar.BeginArray(key)) { - ElementType->WriteValue(ar, addrb); - addrb += ElementSize; + const BYTE *addrb = (const BYTE *)addr; + for (unsigned i = 0; i < ElementCount; ++i) + { + ElementType->WriteValue(ar, nullptr, addrb); + addrb += ElementSize; + } + ar.EndArray(); } } @@ -2116,34 +1863,27 @@ void PArray::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PArray::ReadValue(FArchive &ar, void *addr) const +bool PArray::ReadValue(FSerializer &ar, const char *key, void *addr) const { - bool readsomething = false; - BYTE tag; - - ar << tag; - if (tag == VAL_Array) + if (ar.BeginArray(key)) { - unsigned count = ar.ReadCount(); - unsigned i; + bool readsomething = false; + unsigned count = ar.ArraySize(); + unsigned loop = MIN(count, ElementCount); BYTE *addrb = (BYTE *)addr; - for (i = 0; i < MIN(count, ElementCount); ++i) + for(unsigned i=0;iReadValue(ar, addrb); + readsomething |= ElementType->ReadValue(ar, nullptr, addrb); addrb += ElementSize; } - if (i < ElementCount) + if (loop < count) { DPrintf(DMSG_WARNING, "Array on disk (%u) is bigger than in memory (%u)\n", count, ElementCount); - for (; i < ElementCount; ++i) - { - SkipValue(ar); - } } + ar.EndArray(); return readsomething; } - SkipValue(ar, tag); return false; } @@ -2437,10 +2177,13 @@ void PStruct::SetDefaultValue(void *base, unsigned offset, TArray &fields) +void PStruct::WriteFields(FSerializer &ar, const void *addr, const TArray &fields) { for (unsigned i = 0; i < fields.Size(); ++i) { @@ -2475,11 +2217,9 @@ void PStruct::WriteFields(FArchive &ar, const void *addr, const TArray // Skip fields with native serialization if (!(field->Flags & VARF_Native)) { - ar.WriteName(field->SymbolName); - field->Type->WriteValue(ar, (const BYTE *)addr + field->Offset); + field->Type->WriteValue(ar, field->SymbolName.GetChars(), (const BYTE *)addr + field->Offset); } } - ar.WriteName(NULL); } //========================================================================== @@ -2488,36 +2228,33 @@ void PStruct::WriteFields(FArchive &ar, const void *addr, const TArray // //========================================================================== -bool PStruct::ReadFields(FArchive &ar, void *addr) const +bool PStruct::ReadFields(FSerializer &ar, void *addr) const { bool readsomething = false; - const char *label = ar.ReadName(); - if (label == NULL) - { // If there is nothing to restore, we count it as success. - return true; - } - for (; label != NULL; label = ar.ReadName()) + bool foundsomething = false; + const char *label; + while ((label = ar.GetKey())) { + foundsomething = true; + const PSymbol *sym = Symbols.FindSymbol(FName(label, true), true); if (sym == NULL) { DPrintf(DMSG_ERROR, "Cannot find field %s in %s\n", label, TypeName.GetChars()); - SkipValue(ar); } else if (!sym->IsKindOf(RUNTIME_CLASS(PField))) { DPrintf(DMSG_ERROR, "Symbol %s in %s is not a field\n", label, TypeName.GetChars()); - SkipValue(ar); } else { - readsomething |= static_cast(sym)->Type->ReadValue(ar, + readsomething |= static_cast(sym)->Type->ReadValue(ar, nullptr, (BYTE *)addr + static_cast(sym)->Offset); } } - return readsomething; + return readsomething || !foundsomething; } //========================================================================== @@ -2741,7 +2478,7 @@ END_POINTERS // //========================================================================== -static void RecurseWriteFields(const PClass *type, FArchive &ar, const void *addr) +static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void *addr) { if (type != NULL) { @@ -2755,19 +2492,33 @@ static void RecurseWriteFields(const PClass *type, FArchive &ar, const void *add // a more-derived class has variables that shadow a less- // derived class. Whether or not that is a language feature // that will actually be allowed remains to be seen. - ar.UserWriteClass(const_cast(type)); - PStruct::WriteFields(ar, addr, type->Fields); + FString key; + key.Format("class:%s", type->TypeName.GetChars()); + if (ar.BeginObject(key.GetChars())) + { + PStruct::WriteFields(ar, addr, type->Fields); + ar.EndObject(); + } break; } } } } -void PClass::WriteValue(FArchive &ar, const void *addr) const +void PClass::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (ar.BeginObject(key)) + { + RecurseWriteFields(this, ar, addr); + ar.EndObject(); + } +} + +// Same as WriteValue, but does not create a new object in the serializer +// This is so that user variables do not contain unnecessary subblocks. +void PClass::WriteAllFields(FSerializer &ar, const void *addr) const { - ar.WriteByte(VAL_Class); RecurseWriteFields(this, ar, addr); - ar.UserWriteClass(NULL); } //========================================================================== @@ -2776,20 +2527,40 @@ void PClass::WriteValue(FArchive &ar, const void *addr) const // //========================================================================== -bool PClass::ReadValue(FArchive &ar, void *addr) const +bool PClass::ReadValue(FSerializer &ar, const char *key, void *addr) const { - BYTE tag; - ar << tag; - if (tag != VAL_Class) + if (ar.BeginObject(key)) { - SkipValue(ar, tag); + bool ret = ReadAllFields(ar, addr); + ar.EndObject(); + return ret; + } + return true; +} + +bool PClass::ReadAllFields(FSerializer &ar, void *addr) const +{ + bool readsomething = false; + bool foundsomething = false; + const char *key; + key = ar.GetKey(); + if (strcmp(key, "classtype")) + { + // this does not represent a DObject + Printf(TEXTCOLOR_RED "trying to read user variables but got a non-object (first key is '%s')", key); + ar.mErrors++; return false; } - else + while ((key = ar.GetKey())) { - bool readsomething = false; - PClass *type; - for (ar.UserReadClass(type); type != NULL; ar.UserReadClass(type)) + if (strncmp(key, "class:", 6)) + { + // We have read all user variable blocks. + break; + } + foundsomething = true; + PClass *type = PClass::FindClass(key + 6); + if (type != nullptr) { // Only read it if the type is related to this one. const PClass *parent; @@ -2800,19 +2571,27 @@ bool PClass::ReadValue(FArchive &ar, void *addr) const break; } } - if (parent != NULL) + if (parent != nullptr) { - readsomething |= type->ReadFields(ar, addr); + if (ar.BeginObject(nullptr)) + { + readsomething |= type->ReadFields(ar, addr); + ar.EndObject(); + } } else { DPrintf(DMSG_ERROR, "Unknown superclass %s of class %s\n", type->TypeName.GetChars(), TypeName.GetChars()); - SkipValue(ar, VAL_Struct); } } - return readsomething; + else + { + DPrintf(DMSG_ERROR, "Unknown superclass %s of class %s\n", + key+6, TypeName.GetChars()); + } } + return readsomething || !foundsomething; } //========================================================================== diff --git a/src/dobjtype.h b/src/dobjtype.h index d0ccee7465..62cfa67534 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -193,15 +193,11 @@ public: // a tag indicating its type. The tag is there so that variable types can be changed // without completely breaking savegames, provided that the change isn't between // totally unrelated types. - virtual void WriteValue(FArchive &ar, const void *addr) const; + virtual void WriteValue(FSerializer &ar, const char *key,const void *addr) const; // Returns true if the stored value was compatible. False otherwise. // If the value was incompatible, then the memory at *addr is unchanged. - virtual bool ReadValue(FArchive &ar, void *addr) const; - - // Skips over a value written with WriteValue - static void SkipValue(FArchive &ar); - static void SkipValue(FArchive &ar, int tag); + virtual bool ReadValue(FSerializer &ar, const char *key,void *addr) const; // Sets the default value for this type at (base + offset) // If the default value is binary 0, then this function doesn't need @@ -354,8 +350,8 @@ class PInt : public PBasicType public: PInt(unsigned int size, bool unsign); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; virtual void SetValue(void *addr, int val); virtual void SetValue(void *addr, double val); @@ -383,8 +379,8 @@ class PFloat : public PBasicType public: PFloat(unsigned int size); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; virtual void SetValue(void *addr, int val); virtual void SetValue(void *addr, double val); @@ -421,8 +417,8 @@ public: virtual int GetRegType() const; - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; void SetDefaultValue(void *base, unsigned offset, TArray *special=NULL) const override; void InitializeValue(void *addr, const void *def) const override; void DestroyValue(void *addr) const override; @@ -436,8 +432,8 @@ class PName : public PInt public: PName(); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; }; class PSound : public PInt @@ -446,8 +442,8 @@ class PSound : public PInt public: PSound(); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; }; class PColor : public PInt @@ -465,8 +461,8 @@ class PStatePointer : public PBasicType public: PStatePointer(); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; virtual int GetStoreOp() const; virtual int GetLoadOp() const; @@ -489,8 +485,8 @@ public: virtual bool IsMatch(intptr_t id1, intptr_t id2) const; virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const; - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; protected: PPointer(); @@ -559,8 +555,8 @@ public: virtual bool IsMatch(intptr_t id1, intptr_t id2) const; virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const; - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; void SetDefaultValue(void *base, unsigned offset, TArray *special) const override; @@ -622,12 +618,12 @@ public: size_t PropagateMark(); - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; void SetDefaultValue(void *base, unsigned offset, TArray *specials) const override; - static void WriteFields(FArchive &ar, const void *addr, const TArray &fields); - bool ReadFields(FArchive &ar, void *addr) const; + static void WriteFields(FSerializer &ar, const void *addr, const TArray &fields); + bool ReadFields(FSerializer &ar, void *addr) const; protected: PStruct(); }; @@ -692,8 +688,10 @@ public: typedef PClassClass MetaClass; MetaClass *GetClass() const; - void WriteValue(FArchive &ar, const void *addr) const override; - bool ReadValue(FArchive &ar, void *addr) const override; + void WriteValue(FSerializer &ar, const char *key,const void *addr) const override; + void WriteAllFields(FSerializer &ar, const void *addr) const; + bool ReadValue(FSerializer &ar, const char *key,void *addr) const override; + bool ReadAllFields(FSerializer &ar, void *addr) const; virtual void DeriveData(PClass *newclass) {} static void StaticInit(); diff --git a/src/doomdata.h b/src/doomdata.h index f809f05eaf..eca7dcd4aa 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -339,7 +339,6 @@ struct mapthinghexen_t BYTE args[5]; }; -class FArchive; struct FDoomEdEntry; // Internal representation of a mapthing diff --git a/src/doomtype.h b/src/doomtype.h index 34e4e72e86..cbf1fbd5ed 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -211,13 +211,11 @@ struct PalEntry #endif }; -class FArchive; class PClassInventory; class FTextureID { friend class FTextureManager; - friend FArchive &operator<< (FArchive &arc, FTextureID &tex); friend FTextureID GetHUDIcon(PClassInventory *cls); friend void R_InitSpriteDefs(); diff --git a/src/dsectoreffect.cpp b/src/dsectoreffect.cpp index d33eeb0613..78e00e6b04 100644 --- a/src/dsectoreffect.cpp +++ b/src/dsectoreffect.cpp @@ -28,7 +28,7 @@ #include "p_3dmidtex.h" #include "r_data/r_interpolate.h" #include "statnums.h" -#include "farchive.h" +#include "serializer.h" #include "doomstat.h" IMPLEMENT_CLASS (DSectorEffect) @@ -64,10 +64,10 @@ DSectorEffect::DSectorEffect (sector_t *sector) m_Sector = sector; } -void DSectorEffect::Serialize (FArchive &arc) +void DSectorEffect::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Sector; + arc("sector", m_Sector); } IMPLEMENT_POINTY_CLASS (DMover) @@ -90,10 +90,10 @@ void DMover::Destroy() Super::Destroy(); } -void DMover::Serialize (FArchive &arc) +void DMover::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << interpolation; + arc("interpolation", interpolation); } void DMover::StopInterpolation(bool force) diff --git a/src/dsectoreffect.h b/src/dsectoreffect.h index 792771fb66..0a5e9bd568 100644 --- a/src/dsectoreffect.h +++ b/src/dsectoreffect.h @@ -10,7 +10,8 @@ class DSectorEffect : public DThinker public: DSectorEffect (sector_t *sector); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void Destroy(); sector_t *GetSector() const { return m_Sector; } @@ -32,7 +33,8 @@ protected: private: protected: DMover (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void Destroy(); }; diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 176df6c186..114d260df7 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -38,7 +38,7 @@ #include "statnums.h" #include "i_system.h" #include "doomerrors.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" @@ -103,102 +103,98 @@ bool FThinkerList::IsEmpty() const return Sentinel == NULL || Sentinel->NextThinker == NULL; } -void DThinker::SaveList(FArchive &arc, DThinker *node) +void DThinker::SaveList(FSerializer &arc, DThinker *node) { if (node != NULL) { while (!(node->ObjectFlags & OF_Sentinel)) { assert(node->NextThinker != NULL && !(node->NextThinker->ObjectFlags & OF_EuthanizeMe)); - arc << node; + ::Serialize(arc, nullptr, node, nullptr); node = node->NextThinker; } } } -void DThinker::SerializeAll(FArchive &arc, bool hubLoad) +//========================================================================== +// +// +// +//========================================================================== + +void DThinker::SerializeThinkers(FSerializer &arc, bool hubLoad) { - DThinker *thinker; - BYTE stat; - int statcount; + //DThinker *thinker; + //BYTE stat; + //int statcount; int i; - // Save lists of thinkers, but not by storing the first one and letting - // the archiver catch the rest. (Which leads to buttloads of recursion - // and makes the file larger.) Instead, we explicitly save each thinker - // in sequence. When restoring an archive, we also have to maintain - // the thinker lists here instead of relying on the archiver to do it - // for us. - - if (arc.IsStoring()) + if (arc.isWriting()) { - for (statcount = i = 0; i <= MAX_STATNUM; i++) - { - statcount += (!Thinkers[i].IsEmpty() || !FreshThinkers[i].IsEmpty()); - } - arc << statcount; + arc.BeginArray("thinkers"); for (i = 0; i <= MAX_STATNUM; i++) { - if (!Thinkers[i].IsEmpty() || !FreshThinkers[i].IsEmpty()) - { - stat = i; - arc << stat; - SaveList(arc, Thinkers[i].GetHead()); - SaveList(arc, FreshThinkers[i].GetHead()); - thinker = NULL; - arc << thinker; // Save a final NULL for this list - } + arc.BeginArray(nullptr); + SaveList(arc, Thinkers[i].GetHead()); + SaveList(arc, FreshThinkers[i].GetHead()); + arc.EndArray(); } + arc.EndArray(); } else { - // Prevent the constructor from inserting thinkers into a list. - bSerialOverride = true; - - try + if (arc.BeginArray("thinkers")) { - arc << statcount; - while (statcount > 0) + for (i = 0; i <= MAX_STATNUM; i++) { - arc << stat << thinker; - while (thinker != NULL) + if (arc.BeginArray(nullptr)) { - // This may be a player stored in their ancillary list. Remove - // them first before inserting them into the new list. - if (thinker->NextThinker != NULL) + int size = arc.ArraySize(); + for (int j = 0; j < size; j++) { - thinker->Remove(); + DThinker *thinker; + arc(nullptr, thinker); + if (thinker != nullptr) + { + // This may be a player stored in their ancillary list. Remove + // them first before inserting them into the new list. + if (thinker->NextThinker != nullptr) + { + thinker->Remove(); + } + // Thinkers with the OF_JustSpawned flag set go in the FreshThinkers + // list. Anything else goes in the regular Thinkers list. + if (thinker->ObjectFlags & OF_EuthanizeMe) + { + // This thinker was destroyed during the loading process. Do + // not link it into any list. + } + else if (thinker->ObjectFlags & OF_JustSpawned) + { + FreshThinkers[i].AddTail(thinker); + thinker->PostSerialize(); + } + else + { + Thinkers[i].AddTail(thinker); + thinker->PostSerialize(); + } + } } - // Thinkers with the OF_JustSpawned flag set go in the FreshThinkers - // list. Anything else goes in the regular Thinkers list. - if (thinker->ObjectFlags & OF_EuthanizeMe) - { - // This thinker was destroyed during the loading process. Do - // not link it in to any list. - } - else if (thinker->ObjectFlags & OF_JustSpawned) - { - FreshThinkers[stat].AddTail(thinker); - } - else - { - Thinkers[stat].AddTail(thinker); - } - arc << thinker; + arc.EndArray(); } - statcount--; } + arc.EndArray(); } - catch (class CDoomError &) - { - bSerialOverride = false; - DestroyAllThinkers(); - throw; - } - bSerialOverride = false; } } +//========================================================================== +// +// +// +//========================================================================== + DThinker::DThinker (int statnum) throw() { NextThinker = NULL; @@ -261,6 +257,10 @@ void DThinker::PostBeginPlay () { } +void DThinker::PostSerialize() +{ +} + DThinker *DThinker::FirstThinker (int statnum) { DThinker *node; @@ -332,24 +332,6 @@ void DThinker::DestroyAllThinkers () GC::FullGC(); } -// Destroy all thinkers except for player-controlled actors -// Players are simply removed from the list of thinkers and -// will be added back after serialization is complete. -void DThinker::DestroyMostThinkers () -{ - int i; - - for (i = 0; i <= MAX_STATNUM; i++) - { - if (i != STAT_TRAVELLING) - { - DestroyMostThinkersInList (Thinkers[i], i); - DestroyMostThinkersInList (FreshThinkers[i], i); - } - } - GC::FullGC(); -} - void DThinker::DestroyThinkersInList (FThinkerList &list) { if (list.Sentinel != NULL) @@ -364,39 +346,6 @@ void DThinker::DestroyThinkersInList (FThinkerList &list) } } -void DThinker::DestroyMostThinkersInList (FThinkerList &list, int stat) -{ - if (stat != STAT_PLAYER) - { - DestroyThinkersInList (list); - } - else if (list.Sentinel != NULL) - { // If it's a voodoo doll, destroy it. Otherwise, simply remove - // it from the list. G_FinishTravel() will find it later from - // a players[].mo link and destroy it then, after copying various - // information to a new player. - for (DThinker *probe = list.Sentinel->NextThinker; probe != list.Sentinel; probe = list.Sentinel->NextThinker) - { - if (!probe->IsKindOf(RUNTIME_CLASS(APlayerPawn)) || // <- should not happen - static_cast(probe)->player == NULL || - static_cast(probe)->player->mo != probe) - { - probe->Destroy(); - } - else - { - probe->Remove(); - // Technically, this doesn't need to be in any list now, since - // it's only going to be found later and destroyed before ever - // needing to tick again, but by moving it to a separate list, - // I can keep my debug assertions that all thinkers are either - // euthanizing or in a list. - Thinkers[MAX_STATNUM+1].AddTail(probe); - } - } - } -} - void DThinker::RunThinkers () { int i, count; @@ -473,8 +422,12 @@ void DThinker::Tick () size_t DThinker::PropagateMark() { - assert(NextThinker != NULL && !(NextThinker->ObjectFlags & OF_EuthanizeMe)); - assert(PrevThinker != NULL && !(PrevThinker->ObjectFlags & OF_EuthanizeMe)); + // Do not choke on partially initialized objects (as happens when loading a savegame fails) + if (NextThinker != nullptr || PrevThinker != nullptr) + { + assert(NextThinker != nullptr && !(NextThinker->ObjectFlags & OF_EuthanizeMe)); + assert(PrevThinker != nullptr && !(PrevThinker->ObjectFlags & OF_EuthanizeMe)); + } GC::Mark(NextThinker); GC::Mark(PrevThinker); return Super::PropagateMark(); diff --git a/src/dthinker.h b/src/dthinker.h index ac5adc431b..3f96472977 100644 --- a/src/dthinker.h +++ b/src/dthinker.h @@ -43,6 +43,7 @@ class player_t; struct pspdef_s; struct FState; class DThinker; +class FSerializer; class FThinkerIterator; @@ -69,6 +70,7 @@ public: virtual ~DThinker (); virtual void Tick (); virtual void PostBeginPlay (); // Called just before the first tick + virtual void PostSerialize(); size_t PropagateMark(); void ChangeStatNum (int statnum); @@ -76,33 +78,32 @@ public: static void RunThinkers (); static void RunThinkers (int statnum); static void DestroyAllThinkers (); - static void DestroyMostThinkers (); static void DestroyThinkersInList(int statnum) { DestroyThinkersInList(Thinkers[statnum]); DestroyThinkersInList(FreshThinkers[statnum]); } - static void SerializeAll (FArchive &arc, bool keepPlayers); + static void SerializeThinkers(FSerializer &arc, bool keepPlayers); static void MarkRoots(); static DThinker *FirstThinker (int statnum); + static bool bSerialOverride; private: enum no_link_type { NO_LINK }; DThinker(no_link_type) throw(); static void DestroyThinkersInList (FThinkerList &list); - static void DestroyMostThinkersInList (FThinkerList &list, int stat); static int TickThinkers (FThinkerList *list, FThinkerList *dest); // Returns: # of thinkers ticked - static void SaveList(FArchive &arc, DThinker *node); + static void SaveList(FSerializer &arc, DThinker *node); void Remove(); 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; friend class DObject; + friend class FSerializer; DThinker *NextThinker, *PrevThinker; }; diff --git a/src/farchive.cpp b/src/farchive.cpp deleted file mode 100644 index de3e248a47..0000000000 --- a/src/farchive.cpp +++ /dev/null @@ -1,1589 +0,0 @@ -/* -** farchive.cpp -** Implements an archiver for DObject serialization. -** -**--------------------------------------------------------------------------- -** Copyright 1998-2009 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -** The structure of the archive file generated is influenced heavily by the -** description of the MFC archive format published somewhere in the MSDN -** library. -** -** Two major shortcomings of the format I use are that there is no version -** control and no support for storing the non-default portions of objects. -** The latter would allow for easier extension of objects in future -** releases even without a versioning system. -*/ - -#include -#include -#include -#include - -#include "doomtype.h" -#include "farchive.h" -#include "m_swap.h" -#include "m_crc32.h" -#include "cmdlib.h" -#include "i_system.h" -#include "c_cvars.h" -#include "c_dispatch.h" -#include "d_player.h" -#include "m_misc.h" -#include "dobject.h" - -// These are special tokens found in the data stream of an archive. -// Whenever a new object is encountered, it gets created using new and -// is then asked to serialize itself before processing of the previous -// object continues. This can result in some very deep recursion if -// you aren't careful about how you organize your data. - -#define NEW_OBJ ((BYTE)1) // Data for a new object follows -#define NEW_CLS_OBJ ((BYTE)2) // Data for a new class and object follows -#define OLD_OBJ ((BYTE)3) // Reference to an old object follows -#define NULL_OBJ ((BYTE)4) // Load as NULL -#define M1_OBJ ((BYTE)44) // Load as (DObject*)-1 - -#define NEW_PLYR_OBJ ((BYTE)5) // Data for a new player follows -#define NEW_PLYR_CLS_OBJ ((BYTE)6) // Data for a new class and player follows - -#define NEW_NAME ((BYTE)27) // A new name follows -#define OLD_NAME ((BYTE)28) // Reference to an old name follows -#define NIL_NAME ((BYTE)33) // Load as NULL - -#define NEW_SPRITE ((BYTE)11) // A new sprite name follows -#define OLD_SPRITE ((BYTE)12) // Reference to an old sprite name follows - -#ifdef __BIG_ENDIAN__ -static inline WORD SWAP_WORD(WORD x) { return x; } -static inline DWORD SWAP_DWORD(DWORD x) { return x; } -static inline QWORD SWAP_QWORD(QWORD x) { return x; } -static inline void SWAP_FLOAT(float x) { } -static inline void SWAP_DOUBLE(double &dst, double src) { dst = src; } -#else -#ifdef _MSC_VER -static inline WORD SWAP_WORD(WORD x) { return _byteswap_ushort(x); } -static inline DWORD SWAP_DWORD(DWORD x) { return _byteswap_ulong(x); } -static inline QWORD SWAP_QWORD(QWORD x) { return _byteswap_uint64(x); } -static inline void SWAP_DOUBLE(double &dst, double &src) -{ - union twiddle { QWORD q; double d; } tdst, tsrc; - tsrc.d = src; - tdst.q = _byteswap_uint64(tsrc.q); - dst = tdst.d; -} -#else -static inline WORD SWAP_WORD(WORD x) { return (((x)<<8) | ((x)>>8)); } -static inline DWORD SWAP_DWORD(DWORD x) { return x = (((x)>>24) | (((x)>>8)&0xff00) | (((x)<<8)&0xff0000) | ((x)<<24)); } -static inline QWORD SWAP_QWORD(QWORD x) -{ - union { QWORD q; DWORD d[2]; } t, u; - t.q = x; - u.d[0] = SWAP_DWORD(t.d[1]); - u.d[1] = SWAP_DWORD(t.d[0]); - return u.q; -} -static inline void SWAP_DOUBLE(double &dst, double &src) -{ - union twiddle { double f; DWORD d[2]; } tdst, tsrc; - DWORD t; - - tsrc.f = src; - t = tsrc.d[0]; - tdst.d[0] = SWAP_DWORD(tsrc.d[1]); - tdst.d[1] = SWAP_DWORD(t); - dst = tdst.f; -} -#endif -static inline void SWAP_FLOAT(float &x) -{ - union twiddle { DWORD i; float f; } t; - t.f = x; - t.i = SWAP_DWORD(t.i); - x = t.f; -} -#endif - -// Output buffer size for compression; need some extra space. -// I assume the description in zlib.h is accurate. -#define OUT_LEN(a) ((a) + (a) / 1000 + 12) - -void FCompressedFile::BeEmpty () -{ - m_Pos = 0; - m_BufferSize = 0; - m_MaxBufferSize = 0; - m_Buffer = NULL; - m_File = NULL; - m_NoCompress = false; - m_Mode = ENotOpen; -} - -static const char LZOSig[4] = { 'F', 'L', 'Z', 'O' }; -static const char ZSig[4] = { 'F', 'L', 'Z', 'L' }; - -FCompressedFile::FCompressedFile () -{ - BeEmpty (); -} - -FCompressedFile::FCompressedFile (const char *name, EOpenMode mode, bool dontCompress) -{ - BeEmpty (); - Open (name, mode); - m_NoCompress = dontCompress; -} - -FCompressedFile::FCompressedFile (FILE *file, EOpenMode mode, bool dontCompress, bool postopen) -{ - BeEmpty (); - m_Mode = mode; - m_File = file; - m_NoCompress = dontCompress; - if (postopen) - { - PostOpen (); - } -} - -FCompressedFile::~FCompressedFile () -{ - Close (); -} - -bool FCompressedFile::Open (const char *name, EOpenMode mode) -{ - Close (); - if (name == NULL) - return false; - m_Mode = mode; - m_File = fopen (name, mode == EReading ? "rb" : "wb"); - PostOpen (); - return !!m_File; -} - -void FCompressedFile::PostOpen () -{ - if (m_File && m_Mode == EReading) - { - char sig[4]; - fread (sig, 4, 1, m_File); - if (sig[0] != ZSig[0] || sig[1] != ZSig[1] || sig[2] != ZSig[2] || sig[3] != ZSig[3]) - { - fclose (m_File); - m_File = NULL; - if (sig[0] == LZOSig[0] && sig[1] == LZOSig[1] && sig[2] == LZOSig[2] && sig[3] == LZOSig[3]) - { - Printf ("Compressed files from older ZDooms are not supported.\n"); - } - return; - } - else - { - DWORD sizes[2]; - fread (sizes, sizeof(DWORD), 2, m_File); - sizes[0] = SWAP_DWORD (sizes[0]); - sizes[1] = SWAP_DWORD (sizes[1]); - unsigned int len = sizes[0] == 0 ? sizes[1] : sizes[0]; - m_Buffer = (BYTE *)M_Malloc (len+8); - fread (m_Buffer+8, len, 1, m_File); - sizes[0] = SWAP_DWORD (sizes[0]); - sizes[1] = SWAP_DWORD (sizes[1]); - ((DWORD *)m_Buffer)[0] = sizes[0]; - ((DWORD *)m_Buffer)[1] = sizes[1]; - Explode (); - } - } -} - -void FCompressedFile::Close () -{ - if (m_File) - { - if (m_Mode == EWriting) - { - Implode (); - fwrite (ZSig, 4, 1, m_File); - fwrite (m_Buffer, m_BufferSize + 8, 1, m_File); - } - fclose (m_File); - m_File = NULL; - } - if (m_Buffer) - { - M_Free (m_Buffer); - m_Buffer = NULL; - } - BeEmpty (); -} - -void FCompressedFile::Flush () -{ -} - -FFile::EOpenMode FCompressedFile::Mode () const -{ - return m_Mode; -} - -bool FCompressedFile::IsOpen () const -{ - return !!m_File; -} - -FFile &FCompressedFile::Write (const void *mem, unsigned int len) -{ - if (m_Mode == EWriting) - { - if (m_Pos + len > m_MaxBufferSize) - { - do - { - m_MaxBufferSize = m_MaxBufferSize ? m_MaxBufferSize * 2 : 16384; - } - while (m_Pos + len > m_MaxBufferSize); - m_Buffer = (BYTE *)M_Realloc (m_Buffer, m_MaxBufferSize); - } - if (len == 1) - m_Buffer[m_Pos] = *(BYTE *)mem; - else - memcpy (m_Buffer + m_Pos, mem, len); - m_Pos += len; - if (m_Pos > m_BufferSize) - m_BufferSize = m_Pos; - } - else - { - I_Error ("Tried to write to reading cfile"); - } - return *this; -} - -FFile &FCompressedFile::Read (void *mem, unsigned int len) -{ - if (m_Mode == EReading) - { - if (m_Pos + len > m_BufferSize) - { - I_Error ("Attempt to read past end of cfile"); - } - if (len == 1) - *(BYTE *)mem = m_Buffer[m_Pos]; - else - memcpy (mem, m_Buffer + m_Pos, len); - m_Pos += len; - } - else - { - I_Error ("Tried to read from writing cfile"); - } - return *this; -} - -unsigned int FCompressedFile::Tell () const -{ - return m_Pos; -} - -FFile &FCompressedFile::Seek (int pos, ESeekPos ofs) -{ - if (ofs == ESeekRelative) - pos += m_Pos; - else if (ofs == ESeekEnd) - pos = m_BufferSize - pos; - - if (pos < 0) - m_Pos = 0; - else if ((unsigned)pos > m_BufferSize) - m_Pos = m_BufferSize; - else - m_Pos = pos; - - return *this; -} - -CVAR (Bool, nofilecompression, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -void FCompressedFile::Implode () -{ - uLong outlen; - uLong len = m_BufferSize; - Byte *compressed = NULL; - BYTE *oldbuf = m_Buffer; - int r; - - if (!nofilecompression && !m_NoCompress) - { - outlen = OUT_LEN(len); - do - { - compressed = new Bytef[outlen]; - r = compress (compressed, &outlen, m_Buffer, len); - if (r == Z_BUF_ERROR) - { - delete[] compressed; - outlen += 1024; - } - } while (r == Z_BUF_ERROR); - - // If the data could not be compressed, store it as-is. - if (r != Z_OK || outlen >= len) - { - DPrintf (DMSG_SPAMMY, "cfile could not be compressed\n"); - outlen = 0; - } - else - { - DPrintf (DMSG_SPAMMY, "cfile shrank from %lu to %lu bytes\n", len, outlen); - } - } - else - { - outlen = 0; - } - - m_MaxBufferSize = m_BufferSize = ((outlen == 0) ? len : outlen); - m_Buffer = (BYTE *)M_Malloc (m_BufferSize + 8); - m_Pos = 0; - - DWORD *lens = (DWORD *)(m_Buffer); - lens[0] = BigLong((unsigned int)outlen); - lens[1] = BigLong((unsigned int)len); - - if (outlen == 0) - memcpy (m_Buffer + 8, oldbuf, len); - else - memcpy (m_Buffer + 8, compressed, outlen); - if (compressed) - delete[] compressed; - M_Free (oldbuf); -} - -void FCompressedFile::Explode () -{ - uLong expandsize, cprlen; - unsigned char *expand; - - if (m_Buffer) - { - unsigned int *ints = (unsigned int *)(m_Buffer); - cprlen = BigLong(ints[0]); - expandsize = BigLong(ints[1]); - - expand = (unsigned char *)M_Malloc (expandsize); - if (cprlen) - { - int r; - uLong newlen; - - newlen = expandsize; - r = uncompress (expand, &newlen, m_Buffer + 8, cprlen); - if (r != Z_OK || newlen != expandsize) - { - M_Free (expand); - I_Error ("Could not decompress buffer: %s", M_ZLibError(r).GetChars()); - } - } - else - { - memcpy (expand, m_Buffer + 8, expandsize); - } - if (FreeOnExplode ()) - M_Free (m_Buffer); - m_Buffer = expand; - m_BufferSize = expandsize; - } -} - -FCompressedMemFile::FCompressedMemFile () -{ - m_SourceFromMem = false; - m_ImplodedBuffer = NULL; -} - -/* -FCompressedMemFile::FCompressedMemFile (const char *name, EOpenMode mode) - : FCompressedFile (name, mode) -{ - m_SourceFromMem = false; - m_ImplodedBuffer = NULL; -} -*/ - -FCompressedMemFile::~FCompressedMemFile () -{ - if (m_ImplodedBuffer != NULL) - { - M_Free (m_ImplodedBuffer); - } -} - -bool FCompressedMemFile::Open (const char *name, EOpenMode mode) -{ - if (mode == EWriting) - { - if (name) - { - I_Error ("FCompressedMemFile cannot write to disk"); - } - else - { - return Open (); - } - } - else - { - bool res = FCompressedFile::Open (name, EReading); - if (res) - { - fclose (m_File); - m_File = NULL; - } - return res; - } - return false; -} - -bool FCompressedMemFile::Open (void *memblock) -{ - Close (); - m_Mode = EReading; - m_Buffer = (BYTE *)memblock; - m_SourceFromMem = true; - Explode (); - m_SourceFromMem = false; - return !!m_Buffer; -} - -bool FCompressedMemFile::Open () -{ - Close (); - m_Mode = EWriting; - m_BufferSize = 0; - m_MaxBufferSize = 16384; - m_Buffer = (unsigned char *)M_Malloc (16384); - m_Pos = 0; - return true; -} - -bool FCompressedMemFile::Reopen () -{ - if (m_Buffer == NULL && m_ImplodedBuffer) - { - m_Mode = EReading; - m_Buffer = m_ImplodedBuffer; - m_SourceFromMem = true; - try - { - Explode (); - } - catch(...) - { - // If we just leave things as they are, m_Buffer and m_ImplodedBuffer - // both point to the same memory block and both will try to free it. - m_Buffer = NULL; - m_SourceFromMem = false; - throw; - } - m_SourceFromMem = false; - return true; - } - return false; -} - -void FCompressedMemFile::Close () -{ - if (m_Mode == EWriting) - { - Implode (); - m_ImplodedBuffer = m_Buffer; - m_Buffer = NULL; - } -} - -void FCompressedMemFile::Serialize (FArchive &arc) -{ - if (arc.IsStoring ()) - { - if (m_ImplodedBuffer == NULL) - { - I_Error ("FCompressedMemFile must be compressed before storing"); - } - arc.Write (ZSig, 4); - - DWORD sizes[2]; - sizes[0] = SWAP_DWORD (((DWORD *)m_ImplodedBuffer)[0]); - sizes[1] = SWAP_DWORD (((DWORD *)m_ImplodedBuffer)[1]); - arc.Write (m_ImplodedBuffer, (sizes[0] ? sizes[0] : sizes[1])+8); - } - else - { - Close (); - m_Mode = EReading; - - char sig[4]; - DWORD sizes[2] = { 0, 0 }; - - arc.Read (sig, 4); - - if (sig[0] != ZSig[0] || sig[1] != ZSig[1] || sig[2] != ZSig[2] || sig[3] != ZSig[3]) - I_Error ("Expected to extract a compressed file"); - - arc << sizes[0] << sizes[1]; - DWORD len = sizes[0] == 0 ? sizes[1] : sizes[0]; - - m_Buffer = (BYTE *)M_Malloc (len+8); - ((DWORD *)m_Buffer)[0] = SWAP_DWORD(sizes[0]); - ((DWORD *)m_Buffer)[1] = SWAP_DWORD(sizes[1]); - arc.Read (m_Buffer+8, len); - m_ImplodedBuffer = m_Buffer; - m_Buffer = NULL; - m_Mode = EWriting; - } -} - -bool FCompressedMemFile::IsOpen () const -{ - return !!m_Buffer; -} - -void FCompressedMemFile::GetSizes(unsigned int &compressed, unsigned int &uncompressed) const -{ - if (m_ImplodedBuffer != NULL) - { - compressed = BigLong(*(unsigned int *)m_ImplodedBuffer); - uncompressed = BigLong(*(unsigned int *)(m_ImplodedBuffer + 4)); - } - else - { - compressed = 0; - uncompressed = m_BufferSize; - } -} - -FPNGChunkFile::FPNGChunkFile (FILE *file, DWORD id) - : FCompressedFile (file, EWriting, true, false), m_ChunkID (id) -{ -} - -FPNGChunkFile::FPNGChunkFile (FILE *file, DWORD id, size_t chunklen) - : FCompressedFile (file, EReading, true, false), m_ChunkID (id) -{ - m_Buffer = (BYTE *)M_Malloc (chunklen); - m_BufferSize = (unsigned int)chunklen; - fread (m_Buffer, chunklen, 1, m_File); - // Skip the CRC for now. Maybe later it will be used. - fseek (m_File, 4, SEEK_CUR); -} - -// Unlike FCompressedFile::Close, m_File is left open -void FPNGChunkFile::Close () -{ - DWORD data[2]; - DWORD crc; - - if (m_File) - { - if (m_Mode == EWriting) - { - crc = CalcCRC32 ((BYTE *)&m_ChunkID, 4); - crc = AddCRC32 (crc, (BYTE *)m_Buffer, m_BufferSize); - - data[0] = BigLong(m_BufferSize); - data[1] = m_ChunkID; - fwrite (data, 8, 1, m_File); - fwrite (m_Buffer, m_BufferSize, 1, m_File); - crc = SWAP_DWORD (crc); - fwrite (&crc, 4, 1, m_File); - } - m_File = NULL; - } - FCompressedFile::Close (); -} - -FPNGChunkArchive::FPNGChunkArchive (FILE *file, DWORD id) - : FArchive (), Chunk (file, id) -{ - AttachToFile (Chunk); -} - -FPNGChunkArchive::FPNGChunkArchive (FILE *file, DWORD id, size_t len) - : FArchive (), Chunk (file, id, len) -{ - AttachToFile (Chunk); -} - -FPNGChunkArchive::~FPNGChunkArchive () -{ - // Close before FArchive's destructor, because Chunk will be - // destroyed before the FArchive is destroyed. - Close (); -} - -//============================================ -// -// FArchive -// -//============================================ - -FArchive::FArchive () -{ -} - -FArchive::FArchive (FFile &file) -{ - AttachToFile (file); -} - -void FArchive::AttachToFile (FFile &file) -{ - m_HubTravel = false; - m_File = &file; - if (file.Mode() == FFile::EReading) - { - m_Loading = true; - m_Storing = false; - } - else - { - m_Loading = false; - m_Storing = true; - } - m_Persistent = file.IsPersistent(); - - ClassToArchive.Clear(); - ArchiveToClass.Clear(); - - ObjectToArchive.Clear(); - ArchiveToObject.Clear(); - - memset(m_NameHash, 0xFF, sizeof(m_NameHash)); - m_Names.Clear(); - m_NameStorage.Clear(); - - m_NumSprites = 0; - m_SpriteMap = new int[sprites.Size()]; - for (size_t s = 0; s < sprites.Size(); ++s) - { - m_SpriteMap[s] = -1; - } -} - -FArchive::~FArchive () -{ - Close (); - if (m_SpriteMap) - delete[] m_SpriteMap; -} - -void FArchive::Write (const void *mem, unsigned int len) -{ - m_File->Write (mem, len); -} - -void FArchive::Read (void *mem, unsigned int len) -{ - m_File->Read (mem, len); -} - -void FArchive::Close () -{ - if (m_File) - { - m_File->Close (); - m_File = NULL; - DPrintf (DMSG_SPAMMY, "Processed %u objects\n", ArchiveToObject.Size()); - } -} - -void FArchive::WriteByte(BYTE val) -{ - m_File->Write(&val, 1); -} - -void FArchive::WriteInt16(WORD val) -{ - WORD out = SWAP_WORD(val); - m_File->Write(&out, 2); -} - -void FArchive::WriteInt32(DWORD val) -{ - int out = SWAP_DWORD(val); - m_File->Write(&out, 4); -} - -void FArchive::WriteInt64(QWORD val) -{ - long long out = SWAP_QWORD(val); - m_File->Write(&out, 8); -} - -void FArchive::WriteCount (DWORD count) -{ - BYTE out; - - do - { - out = count & 0x7f; - if (count >= 0x80) - out |= 0x80; - Write (&out, sizeof(BYTE)); - count >>= 7; - } while (count); - -} - -DWORD FArchive::ReadCount () -{ - BYTE in; - DWORD count = 0; - int ofs = 0; - - do - { - Read (&in, sizeof(BYTE)); - count |= (in & 0x7f) << ofs; - ofs += 7; - } while (in & 0x80); - - return count; -} - -void FArchive::WriteName (const char *name) -{ - BYTE id; - - if (name == NULL) - { - id = NIL_NAME; - Write (&id, 1); - } - else - { - DWORD index = FindName (name); - if (index != NameMap::NO_INDEX) - { - id = OLD_NAME; - Write (&id, 1); - WriteCount (index); - } - else - { - AddName (name); - id = NEW_NAME; - Write (&id, 1); - WriteString (name); - } - } -} - -const char *FArchive::ReadName () -{ - BYTE id; - - operator<< (id); - if (id == NIL_NAME) - { - return NULL; - } - else if (id == OLD_NAME) - { - DWORD index = ReadCount (); - if (index >= m_Names.Size()) - { - I_Error ("Name %u has not been read yet\n", index); - } - return &m_NameStorage[m_Names[index].StringStart]; - } - else if (id == NEW_NAME) - { - DWORD index; - DWORD size = ReadCount (); - char *str; - - index = (DWORD)m_NameStorage.Reserve (size); - str = &m_NameStorage[index]; - Read (str, size-1); - str[size-1] = 0; - AddName (index); - return str; - } - else - { - I_Error ("Expected a name but got something else\n"); - return NULL; - } -} - -void FArchive::WriteString (const char *str) -{ - if (str == NULL) - { - WriteCount (0); - } - else - { - DWORD size = (DWORD)(strlen (str) + 1); - WriteCount (size); - Write (str, size - 1); - } -} - -void FArchive::WriteString(const FString &str) -{ - // The count includes the '\0' terminator, but we don't - // actually write it out. - WriteCount(DWORD(str.Len() + 1)); - Write(str, DWORD(str.Len())); -} - -FArchive &FArchive::operator<< (char *&str) -{ - if (m_Storing) - { - WriteString (str); - } - else - { - DWORD size = ReadCount (); - char *str2; - - if (size == 0) - { - str2 = NULL; - } - else - { - str2 = new char[size]; - size--; - Read (str2, size); - str2[size] = 0; - ReplaceString ((char **)&str, str2); - } - if (str) - { - delete[] str; - } - str = str2; - } - return *this; -} - -FArchive &FArchive::operator<< (FString &str) -{ - if (m_Storing) - { - WriteString (str); - } - else - { - DWORD size = ReadCount(); - - if (size == 0) - { - str = ""; - } - else - { - char *str2 = (char *)alloca(size*sizeof(char)); - size--; - Read (str2, size); - str = FString(str2, size); - } - } - return *this; -} - -FArchive &FArchive::operator<< (BYTE &c) -{ - if (m_Storing) - Write (&c, sizeof(BYTE)); - else - Read (&c, sizeof(BYTE)); - return *this; -} - -FArchive &FArchive::operator<< (WORD &w) -{ - if (m_Storing) - { - WORD temp = SWAP_WORD(w); - Write (&temp, sizeof(WORD)); - } - else - { - Read (&w, sizeof(WORD)); - w = SWAP_WORD(w); - } - return *this; -} - -FArchive &FArchive::operator<< (DWORD &w) -{ - if (m_Storing) - { - DWORD temp = SWAP_DWORD(w); - Write (&temp, sizeof(DWORD)); - } - else - { - Read (&w, sizeof(DWORD)); - w = SWAP_DWORD(w); - } - return *this; -} - -FArchive &FArchive::operator<< (QWORD &w) -{ - if (m_Storing) - { - QWORD temp = SWAP_QWORD(w); - Write (&temp, sizeof(QWORD)); - } - else - { - Read (&w, sizeof(QWORD)); - w = SWAP_QWORD(w); - } - return *this; -} - -FArchive &FArchive::operator<< (float &w) -{ - if (m_Storing) - { - float temp = w; - SWAP_FLOAT(temp); - Write (&temp, sizeof(float)); - } - else - { - Read (&w, sizeof(float)); - SWAP_FLOAT(w); - } - return *this; -} - -FArchive &FArchive::operator<< (double &w) -{ - if (m_Storing) - { - double temp; - SWAP_DOUBLE(temp,w); - Write (&temp, sizeof(double)); - } - else - { - Read (&w, sizeof(double)); - SWAP_DOUBLE(w,w); - } - return *this; -} - -FArchive &FArchive::operator<< (FName &n) -{ // In an archive, a "name" is a string that might be stored multiple times, - // so it is only stored once. It is still treated as a normal string. In the - // rest of the game, a name is a unique identifier for a number. - if (m_Storing) - { - WriteName (n.GetChars()); - } - else - { - n = FName(ReadName()); - } - return *this; -} - -FArchive &FArchive::SerializePointer (void *ptrbase, BYTE **ptr, DWORD elemSize) -{ - DWORD w; - - if (m_Storing) - { - if (*(void **)ptr) - { - w = DWORD(((size_t)*ptr - (size_t)ptrbase) / elemSize); - } - else - { - w = ~0u; - } - WriteCount (w); - } - else - { - w = ReadCount (); - if (w != ~0u) - { - *(void **)ptr = (BYTE *)ptrbase + w * elemSize; - } - else - { - *(void **)ptr = NULL; - } - } - return *this; -} - -FArchive &FArchive::SerializeObject (DObject *&object, PClass *type) -{ - if (!m_ThinkersAllowed && type->IsDescendantOf(RUNTIME_CLASS(DThinker))) - { - assert(true); - I_Error("Tried to serialize a thinker before P_SerializeThinkers"); - } - - if (!type->IsDescendantOf(RUNTIME_CLASS(PClass))) - { // a regular object - if (IsStoring()) - { - return WriteObject(object); - } - else - { - return ReadObject(object, type); - } - } - else - { // a class object - if (IsStoring()) - { - UserWriteClass((PClass *)object); - } - else - { - UserReadClass(object); - } - return *this; - } -} - -FArchive &FArchive::WriteObject (DObject *obj) -{ - player_t *player; - BYTE id[2]; - - if (obj == NULL) - { - id[0] = NULL_OBJ; - Write (id, 1); - } - else if (obj == (DObject*)~0) - { - id[0] = M1_OBJ; - Write (id, 1); - } - else if (obj->ObjectFlags & OF_EuthanizeMe) - { - // Objects that want to die are not saved to the archive, but - // we leave the pointers to them alone. - id[0] = NULL_OBJ; - Write (id, 1); - } - else - { - PClass *type = obj->GetClass(); - DWORD *classarcid; - - if (type == RUNTIME_CLASS(DObject)) - { - //I_Error ("Tried to save an instance of DObject.\n" - // "This should not happen.\n"); - id[0] = NULL_OBJ; - Write (id, 1); - } - else if (NULL == (classarcid = ClassToArchive.CheckKey(type))) - { - // No instances of this class have been written out yet. - // Write out the class, then write out the object. If this - // is an actor controlled by a player, make note of that - // so that it can be overridden when moving around in a hub. - if (obj->IsKindOf (RUNTIME_CLASS (AActor)) && - (player = static_cast(obj)->player) && - player->mo == obj) - { - id[0] = NEW_PLYR_CLS_OBJ; - id[1] = (BYTE)(player - players); - Write (id, 2); - } - else - { - id[0] = NEW_CLS_OBJ; - Write (id, 1); - } - WriteClass (type); -// Printf ("Make class %s (%u)\n", type->Name, m_File->Tell()); - MapObject (obj); - obj->SerializeUserVars (*this); - obj->Serialize (*this); - obj->CheckIfSerialized (); - } - else - { - // An instance of this class has already been saved. If - // this object has already been written, save a reference - // to the saved object. Otherwise, save a reference to the - // class, then save the object. Again, if this is a player- - // controlled actor, remember that. - DWORD *objarcid = ObjectToArchive.CheckKey(obj); - - if (objarcid == NULL) - { - - if (obj->IsKindOf (RUNTIME_CLASS (AActor)) && - (player = static_cast(obj)->player) && - player->mo == obj) - { - id[0] = NEW_PLYR_OBJ; - id[1] = (BYTE)(player - players); - Write (id, 2); - } - else - { - id[0] = NEW_OBJ; - Write (id, 1); - } - WriteCount (*classarcid); -// Printf ("Reuse class %s (%u)\n", type->Name, m_File->Tell()); - MapObject (obj); - obj->SerializeUserVars (*this); - obj->Serialize (*this); - obj->CheckIfSerialized (); - } - else - { - id[0] = OLD_OBJ; - Write (id, 1); - WriteCount (*objarcid); - } - } - } - return *this; -} - -FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) -{ - BYTE objHead; - const PClass *type; - BYTE playerNum; - DWORD index; - DObject *newobj; - - operator<< (objHead); - - switch (objHead) - { - case NULL_OBJ: - obj = NULL; - break; - - case M1_OBJ: - obj = (DObject *)~0; - break; - - case OLD_OBJ: - index = ReadCount(); - if (index >= ArchiveToObject.Size()) - { - I_Error ("Object reference too high (%u; max is %u)\n", index, ArchiveToObject.Size()); - } - obj = ArchiveToObject[index]; - break; - - case NEW_PLYR_CLS_OBJ: - operator<< (playerNum); - if (m_HubTravel) - { - // If travelling inside a hub, use the existing player actor - type = ReadClass (wanttype); -// Printf ("New player class: %s (%u)\n", type->Name, m_File->Tell()); - obj = players[playerNum].mo; - - // But also create a new one so that we can get past the one - // stored in the archive. - AActor *tempobj = static_cast(type->CreateNew ()); - MapObject (obj != NULL ? obj : tempobj); - tempobj->SerializeUserVars (*this); - tempobj->Serialize (*this); - tempobj->CheckIfSerialized (); - // If this player is not present anymore, keep the new body - // around just so that the load will succeed. - if (obj != NULL) - { - // When the temporary player's inventory items were loaded, - // they became owned by the real player. Undo that now. - for (AInventory *item = tempobj->Inventory; item != NULL; item = item->Inventory) - { - item->Owner = tempobj; - } - tempobj->Destroy (); - } - else - { - obj = tempobj; - players[playerNum].mo = static_cast(obj); - } - break; - } - /* fallthrough when not travelling to a previous level */ - case NEW_CLS_OBJ: - type = ReadClass (wanttype); -// Printf ("New class: %s (%u)\n", type->Name, m_File->Tell()); - newobj = obj = type->CreateNew (); - MapObject (obj); - newobj->SerializeUserVars (*this); - newobj->Serialize (*this); - newobj->CheckIfSerialized (); - break; - - case NEW_PLYR_OBJ: - operator<< (playerNum); - if (m_HubTravel) - { - type = ReadStoredClass (wanttype); -// Printf ("Use player class: %s (%u)\n", type->Name, m_File->Tell()); - obj = players[playerNum].mo; - - AActor *tempobj = static_cast(type->CreateNew ()); - MapObject (obj != NULL ? obj : tempobj); - tempobj->SerializeUserVars (*this); - tempobj->Serialize (*this); - tempobj->CheckIfSerialized (); - if (obj != NULL) - { - for (AInventory *item = tempobj->Inventory; - item != NULL; item = item->Inventory) - { - item->Owner = tempobj; - } - tempobj->Destroy (); - } - else - { - obj = tempobj; - players[playerNum].mo = static_cast(obj); - } - break; - } - /* fallthrough when not travelling to a previous level */ - case NEW_OBJ: - type = ReadStoredClass (wanttype); -// Printf ("Use class: %s (%u)\n", type->Name, m_File->Tell()); - obj = type->CreateNew (); - MapObject (obj); - obj->SerializeUserVars (*this); - obj->Serialize (*this); - obj->CheckIfSerialized (); - break; - - default: - I_Error ("Unknown object code (%d) in archive\n", objHead); - } - return *this; -} - -void FArchive::WriteSprite (int spritenum) -{ - BYTE id; - - if ((unsigned)spritenum >= (unsigned)sprites.Size()) - { - spritenum = 0; - } - - if (m_SpriteMap[spritenum] < 0) - { - m_SpriteMap[spritenum] = (int)(m_NumSprites++); - id = NEW_SPRITE; - Write (&id, 1); - Write (sprites[spritenum].name, 4); - - // Write the current sprite number as a hint, because - // these will only change between different versions. - WriteCount (spritenum); - } - else - { - id = OLD_SPRITE; - Write (&id, 1); - WriteCount (m_SpriteMap[spritenum]); - } -} - -int FArchive::ReadSprite () -{ - BYTE id; - - Read (&id, 1); - if (id == OLD_SPRITE) - { - DWORD index = ReadCount (); - if (index >= m_NumSprites) - { - I_Error ("Sprite %u has not been read yet\n", index); - } - return m_SpriteMap[index]; - } - else if (id == NEW_SPRITE) - { - DWORD name; - DWORD hint; - - Read (&name, 4); - hint = ReadCount (); - - if (hint >= NumStdSprites || sprites[hint].dwName != name) - { - for (hint = NumStdSprites; hint-- != 0; ) - { - if (sprites[hint].dwName == name) - { - break; - } - } - if (hint >= sprites.Size()) - { // Don't know this sprite, so just use the first one - hint = 0; - } - } - m_SpriteMap[m_NumSprites++] = hint; - return hint; - } - else - { - I_Error ("Expected a sprite but got something else\n"); - return 0; - } -} - -DWORD FArchive::AddName (const char *name) -{ - DWORD index; - unsigned int hash = MakeKey (name) % EObjectHashSize; - - index = FindName (name, hash); - if (index == NameMap::NO_INDEX) - { - DWORD namelen = (DWORD)(strlen (name) + 1); - DWORD strpos = (DWORD)m_NameStorage.Reserve (namelen); - NameMap mapper = { strpos, (DWORD)m_NameHash[hash] }; - - memcpy (&m_NameStorage[strpos], name, namelen); - m_NameHash[hash] = index = (DWORD)m_Names.Push (mapper); - } - return index; -} - -DWORD FArchive::AddName (unsigned int start) -{ - DWORD hash = MakeKey (&m_NameStorage[start]) % EObjectHashSize; - NameMap mapper = { (DWORD)start, (DWORD)m_NameHash[hash] }; - return (DWORD)(m_NameHash[hash] = m_Names.Push (mapper)); -} - -DWORD FArchive::FindName (const char *name) const -{ - return FindName (name, MakeKey (name) % EObjectHashSize); -} - -DWORD FArchive::FindName (const char *name, unsigned int bucket) const -{ - unsigned int map = m_NameHash[bucket]; - - while (map != NameMap::NO_INDEX) - { - const NameMap *mapping = &m_Names[map]; - if (strcmp (name, &m_NameStorage[mapping->StringStart]) == 0) - { - return (DWORD)map; - } - map = mapping->HashNext; - } - return (DWORD)map; -} - -DWORD FArchive::WriteClass (PClass *info) -{ - if (ClassToArchive.CheckKey(info) != NULL) - { - I_Error ("Attempt to write '%s' twice.\n", info->TypeName.GetChars()); - } - DWORD index = ArchiveToClass.Push(info); - ClassToArchive[info] = index; - WriteString (info->TypeName.GetChars()); - return index; -} - -PClass *FArchive::ReadClass () -{ - struct String { - String() { val = NULL; } - ~String() { if (val) delete[] val; } - char *val; - } typeName; - - operator<< (typeName.val); - FName zaname(typeName.val, true); - if (zaname != NAME_None) - { - PClass *type = PClass::FindClass(zaname); - if (type != NULL) - { - ClassToArchive[type] = ArchiveToClass.Push(type); - return type; - } - } - I_Error ("Unknown class '%s'\n", typeName.val); - return NULL; -} - -PClass *FArchive::ReadClass (const PClass *wanttype) -{ - PClass *type = ReadClass (); - if (!type->IsDescendantOf (wanttype)) - { - I_Error ("Expected to extract an object of type '%s'.\n" - "Found one of type '%s' instead.\n", - wanttype->TypeName.GetChars(), type->TypeName.GetChars()); - } - return type; -} - -PClass *FArchive::ReadStoredClass (const PClass *wanttype) -{ - DWORD index = ReadCount (); - PClass *type = ArchiveToClass[index]; - if (!type->IsDescendantOf (wanttype)) - { - I_Error ("Expected to extract an object of type '%s'.\n" - "Found one of type '%s' instead.\n", - wanttype->TypeName.GetChars(), type->TypeName.GetChars()); - } - return type; -} - -DWORD FArchive::MapObject (DObject *obj) -{ - DWORD i = ArchiveToObject.Push(obj); - ObjectToArchive[obj] = i; - return i; -} - -void FArchive::UserWriteClass (PClass *type) -{ - BYTE id; - - if (type == NULL) - { - id = 2; - Write (&id, 1); - } - else - { - DWORD *arcid; - if (NULL == (arcid = ClassToArchive.CheckKey(type))) - { - id = 1; - Write (&id, 1); - WriteClass (type); - } - else - { - id = 0; - Write (&id, 1); - WriteCount (*arcid); - } - } -} - -void FArchive::UserReadClass (PClass *&type) -{ - BYTE newclass; - - Read (&newclass, 1); - switch (newclass) - { - case 0: - type = ReadStoredClass (RUNTIME_CLASS(DObject)); - break; - case 1: - type = ReadClass (); - break; - case 2: - type = NULL; - break; - default: - I_Error ("Unknown class type %d in archive.\n", newclass); - break; - } -} - -FArchive &operator<< (FArchive &arc, sector_t *&sec) -{ - return arc.SerializePointer (sectors, (BYTE **)&sec, sizeof(*sectors)); -} - -FArchive &operator<< (FArchive &arc, const sector_t *&sec) -{ - return arc.SerializePointer (sectors, (BYTE **)&sec, sizeof(*sectors)); -} - -FArchive &operator<< (FArchive &arc, line_t *&line) -{ - return arc.SerializePointer (lines, (BYTE **)&line, sizeof(*lines)); -} - -FArchive &operator<< (FArchive &arc, vertex_t *&vert) -{ - return arc.SerializePointer (vertexes, (BYTE **)&vert, sizeof(*vertexes)); -} - -FArchive &operator<< (FArchive &arc, side_t *&side) -{ - return arc.SerializePointer (sides, (BYTE **)&side, sizeof(*sides)); -} - -FArchive &operator<<(FArchive &arc, DAngle &ang) -{ - arc << ang.Degrees; - return arc; -} - -FArchive &operator<<(FArchive &arc, DVector3 &vec) -{ - arc << vec.X << vec.Y << vec.Z; - return arc; -} - -FArchive &operator<<(FArchive &arc, DVector2 &vec) -{ - arc << vec.X << vec.Y; - return arc; -} diff --git a/src/farchive.h b/src/farchive.h deleted file mode 100644 index 44ae8c001e..0000000000 --- a/src/farchive.h +++ /dev/null @@ -1,358 +0,0 @@ -/* -** farchive.h -** -**--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#ifndef __FARCHIVE_H__ -#define __FARCHIVE_H__ - -#include -#include "dobject.h" -#include "r_state.h" -#include "tflags.h" - -class FFile -{ -public: - enum EOpenMode - { - EReading, - EWriting, - ENotOpen - }; - - enum ESeekPos - { - ESeekSet, - ESeekRelative, - ESeekEnd - }; - -virtual ~FFile () {} - -virtual bool Open (const char *name, EOpenMode mode) = 0; -virtual void Close () = 0; -virtual void Flush () = 0; -virtual EOpenMode Mode () const = 0; -virtual bool IsPersistent () const = 0; -virtual bool IsOpen () const = 0; - -virtual FFile& Write (const void *, unsigned int) = 0; -virtual FFile& Read (void *, unsigned int) = 0; - -virtual unsigned int Tell () const = 0; -virtual FFile& Seek (int, ESeekPos) = 0; -inline FFile& Seek (unsigned int i, ESeekPos p) { return Seek ((int)i, p); } -}; - -class FCompressedFile : public FFile -{ -public: - FCompressedFile (); - FCompressedFile (const char *name, EOpenMode mode, bool dontcompress = false); - FCompressedFile (FILE *file, EOpenMode mode, bool dontcompress = false, bool postopen=true); - ~FCompressedFile (); - - bool Open (const char *name, EOpenMode mode); - void Close (); - void Flush (); - EOpenMode Mode () const; - bool IsPersistent () const { return true; } - bool IsOpen () const; - unsigned int GetSize () const { return m_BufferSize; } - - FFile &Write (const void *, unsigned int); - FFile &Read (void *, unsigned int); - unsigned int Tell () const; - FFile &Seek (int, ESeekPos); - -protected: - unsigned int m_Pos; - unsigned int m_BufferSize; - unsigned int m_MaxBufferSize; - unsigned char *m_Buffer; - bool m_NoCompress; - EOpenMode m_Mode; - FILE *m_File; - - void Implode (); - void Explode (); - virtual bool FreeOnExplode () { return true; } - void PostOpen (); - -private: - void BeEmpty (); -}; - -class FCompressedMemFile : public FCompressedFile -{ -public: - FCompressedMemFile (); - FCompressedMemFile (FILE *file); // Create for reading - ~FCompressedMemFile (); - - bool Open (const char *name, EOpenMode mode); // Works for reading only - bool Open (void *memblock); // Open for reading only - bool Open (); // Open for writing only - bool Reopen (); // Re-opens imploded file for reading only - void Close (); - bool IsOpen () const; - void GetSizes(unsigned int &one, unsigned int &two) const; - - void Serialize (FArchive &arc); - -protected: - bool FreeOnExplode () { return !m_SourceFromMem; } - -private: - bool m_SourceFromMem; - unsigned char *m_ImplodedBuffer; -}; - -class FPNGChunkFile : public FCompressedFile -{ -public: - FPNGChunkFile (FILE *file, DWORD id); // Create for writing - FPNGChunkFile (FILE *file, DWORD id, size_t chunklen); // Create for reading - - void Close (); - -private: - DWORD m_ChunkID; -}; - -class FArchive -{ -public: - FArchive (FFile &file); - virtual ~FArchive (); - - inline bool IsLoading () const { return m_Loading; } - inline bool IsStoring () const { return m_Storing; } - inline bool IsPeristent () const { return m_Persistent; } - - void SetHubTravel () { m_HubTravel = true; } - - void Close (); - -virtual void Write (const void *mem, unsigned int len); -virtual void Read (void *mem, unsigned int len); - - void WriteString(const FString &str); - void WriteString (const char *str); - - void WriteByte(BYTE val); - void WriteInt16(WORD val); - void WriteInt32(DWORD val); - void WriteInt64(QWORD val); - - void WriteCount (DWORD count); - DWORD ReadCount (); - - void UserWriteClass (PClass *info); - void UserReadClass (PClass *&info); - template void UserReadClass(T *&info) - { - PClass *myclass; - UserReadClass(myclass); - info = dyn_cast(myclass); - } - - FArchive& operator<< (BYTE &c); - FArchive& operator<< (WORD &s); - FArchive& operator<< (DWORD &i); - FArchive& operator<< (QWORD &i); - FArchive& operator<< (QWORD_UNION &i) { return operator<< (i.AsOne); } - FArchive& operator<< (float &f); - FArchive& operator<< (double &d); - FArchive& operator<< (char *&str); - FArchive& operator<< (FName &n); - FArchive& operator<< (FString &str); - FArchive& SerializePointer (void *ptrbase, BYTE **ptr, DWORD elemSize); - FArchive& SerializeObject (DObject *&object, PClass *type); - FArchive& WriteObject (DObject *obj); - FArchive& ReadObject (DObject *&obj, PClass *wanttype); - - void WriteName (const char *name); - const char *ReadName (); // The returned name disappears with the archive, unlike strings - - void WriteSprite (int spritenum); - int ReadSprite (); - -inline FArchive& operator<< (SBYTE &c) { return operator<< ((BYTE &)c); } -inline FArchive& operator<< (SWORD &s) { return operator<< ((WORD &)s); } -inline FArchive& operator<< (SDWORD &i) { return operator<< ((DWORD &)i); } -inline FArchive& operator<< (SQWORD &i) { return operator<< ((QWORD &)i); } -inline FArchive& operator<< (unsigned char *&str) { return operator<< ((char *&)str); } -inline FArchive& operator<< (signed char *&str) { return operator<< ((char *&)str); } -inline FArchive& operator<< (bool &b) { return operator<< ((BYTE &)b); } -inline FArchive& operator<< (DObject* &object) { return ReadObject (object, RUNTIME_CLASS(DObject)); } - - void EnableThinkers() - { - m_ThinkersAllowed = true; - } - - bool ThinkersAllowed() - { - return m_ThinkersAllowed; - } - -protected: - enum { EObjectHashSize = 137 }; - - DWORD MapObject (DObject *obj); - DWORD WriteClass (PClass *info); - PClass *ReadClass (); - PClass *ReadClass (const PClass *wanttype); - PClass *ReadStoredClass (const PClass *wanttype); - DWORD AddName (const char *name); - DWORD AddName (unsigned int start); // Name has already been added to storage - DWORD FindName (const char *name) const; - DWORD FindName (const char *name, unsigned int bucket) const; - - bool m_Persistent; // meant for persistent storage (disk)? - bool m_Loading; // extracting objects? - bool m_Storing; // inserting objects? - bool m_HubTravel; // travelling inside a hub? - FFile *m_File; // unerlying file object - - TMap ClassToArchive; // Maps PClass to archive type index - TArray ArchiveToClass; // Maps archive type index to PClass - - TMap ObjectToArchive; // Maps objects to archive index - TArray ArchiveToObject; // Maps archive index to objects - - struct NameMap - { - DWORD StringStart; // index into m_NameStorage - DWORD HashNext; // next in hash bucket - enum { NO_INDEX = 0xffffffff }; - }; - TArray m_Names; - TArray m_NameStorage; - unsigned int m_NameHash[EObjectHashSize]; - - int *m_SpriteMap; - size_t m_NumSprites; - bool m_ThinkersAllowed = false; - - FArchive (); - void AttachToFile (FFile &file); - -private: - FArchive (const FArchive &) {} - void operator= (const FArchive &) {} -}; - -// Create an FPNGChunkFile and FArchive in one step -class FPNGChunkArchive : public FArchive -{ -public: - FPNGChunkArchive (FILE *file, DWORD chunkid); - FPNGChunkArchive (FILE *file, DWORD chunkid, size_t chunklen); - ~FPNGChunkArchive (); - FPNGChunkFile Chunk; -}; - -inline FArchive &operator<< (FArchive &arc, PalEntry &p) -{ - return arc << p.a << p.r << p.g << p.b; -} - -template -inline FArchive &operator<< (FArchive &arc, T* &object) -{ - return arc.SerializeObject ((DObject*&)object, RUNTIME_TEMPLATE_CLASS(T)); -} - -class FFont; -FArchive &SerializeFFontPtr (FArchive &arc, FFont* &font); -template<> inline FArchive &operator<< (FArchive &arc, FFont* &font) -{ - return SerializeFFontPtr (arc, font); -} - -struct FStrifeDialogueNode; -struct FSwitchDef; -struct FDoorAnimation; -struct FLinePortal; -template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node); -template<> FArchive &operator<< (FArchive &arc, FSwitchDef* &sw); -template<> FArchive &operator<< (FArchive &arc, FDoorAnimation* &da); -FArchive &operator<< (FArchive &arc, FLinePortal &da); -FArchive &operator<< (FArchive &arc, FSectorPortal &da); - - - -template -inline FArchive &operator<< (FArchive &arc, TArray &self) -{ - if (arc.IsStoring()) - { - arc.WriteCount(self.Count); - } - else - { - DWORD numStored = arc.ReadCount(); - self.Resize(numStored); - } - for (unsigned int i = 0; i < self.Count; ++i) - { - arc << self.Array[i]; - } - return arc; -} - -struct sector_t; -struct line_t; -struct vertex_t; -struct side_t; - -FArchive &operator<< (FArchive &arc, sector_t *&sec); -FArchive &operator<< (FArchive &arc, const sector_t *&sec); -FArchive &operator<< (FArchive &arc, line_t *&line); -FArchive &operator<< (FArchive &arc, vertex_t *&vert); -FArchive &operator<< (FArchive &arc, side_t *&side); - -FArchive &operator<<(FArchive &arc, DAngle &ang); -FArchive &operator<<(FArchive &arc, DVector3 &vec); -FArchive &operator<<(FArchive &arc, DVector2 &vec); - - - -template -FArchive& operator<< (FArchive& arc, TFlags& flag) -{ - return flag.Serialize (arc); -} - -#endif //__FARCHIVE_H__ diff --git a/src/files.cpp b/src/files.cpp index 93a8c9729b..527fa47495 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -600,3 +600,58 @@ char *MemoryArrayReader::Gets(char *strbuf, int len) { return GetsFromBuffer((char*)&buf[0], strbuf, len); } + +//========================================================================== +// +// FileWriter (the motivation here is to have a buffer writing subclass) +// +//========================================================================== + +bool FileWriter::OpenDirect(const char *filename) +{ + File = fopen(filename, "wb"); + return (File != NULL); +} + +FileWriter *FileWriter::Open(const char *filename) +{ + FileWriter *fwrit = new FileWriter(); + if (fwrit->OpenDirect(filename)) + { + return fwrit; + } + delete fwrit; + return NULL; +} + +size_t FileWriter::Write(const void *buffer, size_t len) +{ + if (File != NULL) + { + return fwrite(buffer, 1, len, File); + } + else + { + return 0; + } +} + + +size_t FileWriter::Printf(const char *fmt, ...) +{ + va_list ap; + FString out; + + va_start(ap, fmt); + out.VFormat(fmt, ap); + va_end(ap); + return Write(out.GetChars(), out.Len()); +} + +size_t BufferWriter::Write(const void *buffer, size_t len) +{ + unsigned int ofs = mBuffer.Reserve((unsigned)len); + memcpy(&mBuffer[ofs], buffer, len); + return len; +} + diff --git a/src/files.h b/src/files.h index 5889eb6fe7..4c12550c80 100644 --- a/src/files.h +++ b/src/files.h @@ -356,4 +356,46 @@ protected: }; +class FileWriter +{ +protected: + bool OpenDirect(const char *filename); + + FileWriter() + { + File = NULL; + } +public: + virtual ~FileWriter() + { + if (File != NULL) fclose(File); + } + + static FileWriter *Open(const char *filename); + + virtual size_t Write(const void *buffer, size_t len); + size_t Printf(const char *fmt, ...); + +protected: + + FILE *File; + +protected: + bool CloseOnDestruct; +}; + +class BufferWriter : public FileWriter +{ +protected: + TArray mBuffer; +public: + + BufferWriter() {} + virtual size_t Write(const void *buffer, size_t len) override; + TArray *GetBuffer() { return &mBuffer; } +}; + + + + #endif diff --git a/src/fragglescript/t_fspic.cpp b/src/fragglescript/t_fspic.cpp deleted file mode 100644 index 0e7c80346b..0000000000 --- a/src/fragglescript/t_fspic.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* -** t_fspic.cpp -** Fragglescript HUD pics (incomplete and untested!) -** -**--------------------------------------------------------------------------- -** Copyright 2005 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "t_script.h" -#include "doomtype.h" -#include "p_local.h" -#include "farchive.h" -#include "sbar.h" -#include "v_video.h" - - - -struct FHudPic -{ - FTextureID texturenum; - int xpos; - int ypos; - bool draw; - - void Serialize(FArchive & arc) - { - arc << xpos << ypos << draw << texturenum; - } - -}; - -//====================================================================== -// -//====================================================================== -class DHUDPicManager : public DHUDMessage -{ - // This is no real hudmessage but this way I don't need any external code to handle this - // because the hudmessage and thinker code handles everything automatically - DECLARE_CLASS(DHUDPicManager, DHUDMessage) - float basetrans; - -public: - - TArray piclist; - - DHUDPicManager(); - ~DHUDPicManager() {} - void Serialize(FArchive & ar); - virtual void DoDraw (int linenum, int x, int y, int hudheight, float translucent); - void DoDraw (int, int, int, bool, int) { assert(false); } -} ; - -IMPLEMENT_CLASS(DHUDPicManager) - -//====================================================================== -// -//====================================================================== -DHUDPicManager::DHUDPicManager() -{ - HUDWidth=HUDHeight=0; - basetrans=0.8f; - //SetID(0xffffffff); - NumLines=1; - HoldTics=0; // stay forever! - //logtoconsole=false; -} - - -//====================================================================== -// -//====================================================================== -void DHUDPicManager::Serialize(FArchive & ar) -{ - Super::Serialize(ar); - - short count=piclist.Size(); - ar << count << basetrans; - if (ar.IsLoading()) piclist.Resize(count); - for(int i=0;iDrawTexture(tex, piclist[i].xpos, piclist[i].ypos, DTA_320x200, true, - DTA_AlphaF, translucent*basetrans, TAG_DONE); - } -} - - -//====================================================================== -// -//====================================================================== -static TArray & GetPicList() -{ - //TThinkerIterator it; - DHUDPicManager * pm=NULL;//it.Next(); - - if (!pm) pm=new DHUDPicManager; - return pm->piclist; -} - -//====================================================================== -// -// External interface -// -//====================================================================== - -//====================================================================== -// -//====================================================================== -int HU_GetFSPic(FTextureID texturenum, int xpos, int ypos) -{ - TArray &piclist=GetPicList(); - unsigned int i; - - for(i=0;i &piclist=GetPicList(); - - if(handle >= piclist.Size()) return -1; - piclist[handle].texturenum.SetInvalid(); - return 0; -} - - -//====================================================================== -// -//====================================================================== -int HU_ModifyFSPic(unsigned handle, FTextureID texturenum, int xpos, int ypos) -{ - TArray &piclist=GetPicList(); - - if(handle >= piclist.Size()) return -1; - if(!piclist[handle].texturenum.isValid()) return -1; - - piclist[handle].texturenum = texturenum; - piclist[handle].xpos = xpos; - piclist[handle].ypos = ypos; - return 0; -} - -//====================================================================== -// -//====================================================================== -int HU_FSDisplay(unsigned handle, bool newval) -{ - TArray &piclist=GetPicList(); - - if(handle >= piclist.Size()) return -1; - if(!piclist[handle].texturenum.isValid()) return -1; - - piclist[handle].draw = newval; - return 0; -} - diff --git a/src/fragglescript/t_func.cpp b/src/fragglescript/t_func.cpp index deb4907a3e..a02ec947f3 100644 --- a/src/fragglescript/t_func.cpp +++ b/src/fragglescript/t_func.cpp @@ -65,7 +65,7 @@ #include "v_palette.h" #include "v_font.h" #include "r_data/colormaps.h" -#include "farchive.h" +#include "serializer.h" #include "p_setup.h" #include "p_spec.h" #include "r_utility.h" @@ -1780,7 +1780,7 @@ class DLightLevel : public DLighting public: DLightLevel(sector_t * s,int destlevel,int speed); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); void Destroy() { Super::Destroy(); m_Sector->lightingdata=NULL; } }; @@ -1789,11 +1789,11 @@ public: IMPLEMENT_CLASS (DLightLevel) -void DLightLevel::Serialize (FArchive &arc) +void DLightLevel::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << destlevel << speed; - if (arc.IsLoading()) m_Sector->lightingdata=this; + arc("destlevel", destlevel) + ("speed", speed); } @@ -3647,58 +3647,24 @@ void FParser::SF_Pow() //========================================================================== -int HU_GetFSPic(FTextureID lumpnum, int xpos, int ypos); -int HU_DeleteFSPic(unsigned int handle); -int HU_ModifyFSPic(unsigned int handle, FTextureID lumpnum, int xpos, int ypos); -int HU_FSDisplay(unsigned int handle, bool newval); - void FParser::SF_NewHUPic() { - if (CheckArgs(3)) - { - t_return.type = svt_int; - t_return.value.i = HU_GetFSPic( - TexMan.GetTexture(stringvalue(t_argv[0]), FTexture::TEX_MiscPatch, FTextureManager::TEXMAN_TryAny), - intvalue(t_argv[1]), intvalue(t_argv[2])); - } + // disabled because it was never used and never tested } void FParser::SF_DeleteHUPic() { - if (CheckArgs(1)) - { - if (HU_DeleteFSPic(intvalue(t_argv[0])) == -1) - script_error("deletehupic: Invalid sfpic handle: %i\n", intvalue(t_argv[0])); - } + // disabled because it was never used and never tested } void FParser::SF_ModifyHUPic() { - if (t_argc != 4) - { - script_error("modifyhupic: invalid number of arguments\n"); - return; - } - - if (HU_ModifyFSPic(intvalue(t_argv[0]), - TexMan.GetTexture(stringvalue(t_argv[0]), FTexture::TEX_MiscPatch, FTextureManager::TEXMAN_TryAny), - intvalue(t_argv[2]), intvalue(t_argv[3])) == -1) - { - script_error("modifyhypic: invalid sfpic handle %i\n", intvalue(t_argv[0])); - } - return; + // disabled because it was never used and never tested } void FParser::SF_SetHUPicDisplay() { - if (t_argc != 2) - { - script_error("sethupicdisplay: invalud number of arguments\n"); - return; - } - - if (HU_FSDisplay(intvalue(t_argv[0]), intvalue(t_argv[1]) > 0 ? 1 : 0) == -1) - script_error("sethupicdisplay: invalid pic handle %i\n", intvalue(t_argv[0])); + // disabled because it was never used and never tested } diff --git a/src/fragglescript/t_prepro.cpp b/src/fragglescript/t_prepro.cpp index 1826d2fe9e..619b6d570e 100644 --- a/src/fragglescript/t_prepro.cpp +++ b/src/fragglescript/t_prepro.cpp @@ -56,7 +56,7 @@ #include "t_script.h" #include "i_system.h" #include "w_wad.h" -#include "farchive.h" +#include "serializer.h" //========================================================================== @@ -81,10 +81,14 @@ END_POINTERS // //========================================================================== -void DFsSection::Serialize(FArchive &ar) +void DFsSection::Serialize(FSerializer &arc) { - Super::Serialize(ar); - ar << type << start_index << end_index << loop_index << next; + Super::Serialize(arc); + arc("type", type) + ("start_index", start_index) + ("end_index", end_index) + ("loop_index", loop_index) + ("next", next); } //========================================================================== diff --git a/src/fragglescript/t_script.cpp b/src/fragglescript/t_script.cpp index e433ca5ce4..f1376b7eb3 100644 --- a/src/fragglescript/t_script.cpp +++ b/src/fragglescript/t_script.cpp @@ -54,7 +54,7 @@ #include "i_system.h" #include "doomerrors.h" #include "doomstat.h" -#include "farchive.h" +#include "serializer.h" //========================================================================== // @@ -162,6 +162,19 @@ DFsScript::DFsScript() lastiftrue = false; } +//========================================================================== +// +// This is here to delete the locally allocated buffer in case this +// gets forcibly destroyed +// +//========================================================================== + +DFsScript::~DFsScript() +{ + if (data != NULL) delete[] data; + data = NULL; +} + //========================================================================== // // @@ -187,18 +200,23 @@ void DFsScript::Destroy() // //========================================================================== -void DFsScript::Serialize(FArchive &arc) +void DFsScript::Serialize(FSerializer &arc) { Super::Serialize(arc); // don't save a reference to the global script - if (parent == global_script) parent = NULL; + if (parent == global_script) parent = nullptr; - arc << data << scriptnum << len << parent << trigger << lastiftrue; - for(int i=0; i< SECTIONSLOTS; i++) arc << sections[i]; - for(int i=0; i< VARIABLESLOTS; i++) arc << variables[i]; - for(int i=0; i< MAXSCRIPTS; i++) arc << children[i]; + arc("data", data) + ("scriptnum", scriptnum) + ("len", len) + ("parent", parent) + ("trigger", trigger) + ("lastiftrue", lastiftrue) + .Array("sections", sections, SECTIONSLOTS) + .Array("variables", variables, VARIABLESLOTS) + .Array("children", children, MAXSCRIPTS); - if (parent == NULL) parent = global_script; + if (parent == nullptr) parent = global_script; } //========================================================================== @@ -338,12 +356,17 @@ void DRunningScript::Destroy() // //========================================================================== -void DRunningScript::Serialize(FArchive &arc) +void DRunningScript::Serialize(FSerializer &arc) { Super::Serialize(arc); - - arc << script << save_point << wait_type << wait_data << prev << next << trigger; - for(int i=0; i< VARIABLESLOTS; i++) arc << variables[i]; + arc("script", script) + ("save_point", save_point) + ("wait_type", wait_type) + ("wait_data", wait_data) + ("prev", prev) + ("next", next) + ("trigger", trigger) + .Array("variables", variables, VARIABLESLOTS); } @@ -416,10 +439,13 @@ void DFraggleThinker::Destroy() // //========================================================================== -void DFraggleThinker::Serialize(FArchive &arc) +void DFraggleThinker::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << LevelScript << RunningScripts << SpawnedThings << nocheckposition; + arc("levelscript", LevelScript) + ("runningscripts", RunningScripts) + ("spawnedthings", SpawnedThings) + ("nocheckposition", nocheckposition); } //========================================================================== diff --git a/src/fragglescript/t_script.h b/src/fragglescript/t_script.h index 02ea444a2b..3734e16b8a 100644 --- a/src/fragglescript/t_script.h +++ b/src/fragglescript/t_script.h @@ -189,7 +189,7 @@ public: void GetValue(svalue_t &result); void SetValue(const svalue_t &newvalue); - void Serialize(FArchive &ar); + void Serialize(FSerializer &ar); }; //========================================================================== @@ -238,7 +238,7 @@ public: next = NULL; } - void Serialize(FArchive &ar); + void Serialize(FSerializer &ar); }; @@ -336,8 +336,9 @@ public: // true or false DFsScript(); + ~DFsScript(); void Destroy(); - void Serialize(FArchive &ar); + void Serialize(FSerializer &ar); DFsVariable *NewVariable(const char *name, int vtype); void NewFunction(const char *name, void (FParser::*handler)()); @@ -652,7 +653,7 @@ class DRunningScript : public DObject public: DRunningScript(AActor *trigger=NULL, DFsScript *owner = NULL, int index = 0) ; void Destroy(); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); TObjPtr script; @@ -689,7 +690,7 @@ public: void Destroy(); - void Serialize(FArchive & arc); + void Serialize(FSerializer & arc); void Tick(); size_t PropagateMark(); size_t PointerSubstitution (DObject *old, DObject *notOld); diff --git a/src/fragglescript/t_variable.cpp b/src/fragglescript/t_variable.cpp index b41b054909..125f32367d 100644 --- a/src/fragglescript/t_variable.cpp +++ b/src/fragglescript/t_variable.cpp @@ -54,7 +54,7 @@ #include "t_script.h" #include "a_pickups.h" -#include "farchive.h" +#include "serializer.h" //========================================================================== @@ -306,10 +306,15 @@ void DFsVariable::SetValue(const svalue_t &newvalue) // //========================================================================== -void DFsVariable::Serialize(FArchive & ar) +void DFsVariable::Serialize(FSerializer & ar) { Super::Serialize(ar); - ar << Name << type << string << actor << value.i << next; + ar("name", Name) + ("type", type) + ("string", string) + ("actor", actor) + ("value", value.i) + ("next", next); } diff --git a/src/g_doom/a_bossbrain.cpp b/src/g_doom/a_bossbrain.cpp index e6fd5f16df..b0e7259105 100644 --- a/src/g_doom/a_bossbrain.cpp +++ b/src/g_doom/a_bossbrain.cpp @@ -120,7 +120,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BrainSpit) bool isdefault = false; // shoot a cube at current target - targ = state->GetNextInList(PClass::FindClass("BossTarget"), G_SkillProperty(SKILLP_EasyBossBrain)); + targ = state->GetNextInList(PClass::FindActor("BossTarget"), G_SkillProperty(SKILLP_EasyBossBrain)); if (targ != NULL) { diff --git a/src/g_doom/a_doomglobal.h b/src/g_doom/a_doomglobal.h index 23e181cc8b..31d41f6c0a 100644 --- a/src/g_doom/a_doomglobal.h +++ b/src/g_doom/a_doomglobal.h @@ -29,7 +29,8 @@ public: void Tick (); void SetWeapon (EMarineWeapon); void SetSprite (PClassActor *source); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); int CurrentWeapon; diff --git a/src/g_doom/a_doommisc.cpp b/src/g_doom/a_doommisc.cpp index 93e7abdaa5..129c32f1ed 100644 --- a/src/g_doom/a_doommisc.cpp +++ b/src/g_doom/a_doommisc.cpp @@ -14,10 +14,10 @@ #include "a_specialspot.h" #include "templates.h" #include "m_bbox.h" -#include "farchive.h" #include "portal.h" #include "d_player.h" #include "p_maputl.h" +#include "serializer.h" #include "g_shared/a_pickups.h" // Include all the other Doom stuff here to reduce compile time diff --git a/src/g_doom/a_scriptedmarine.cpp b/src/g_doom/a_scriptedmarine.cpp index a3d33f5553..2c293a2deb 100644 --- a/src/g_doom/a_scriptedmarine.cpp +++ b/src/g_doom/a_scriptedmarine.cpp @@ -21,19 +21,14 @@ static FRandom pr_m_fireshotgun2 ("SMarineFireSSG"); IMPLEMENT_CLASS (AScriptedMarine) -void AScriptedMarine::Serialize (FArchive &arc) +void AScriptedMarine::Serialize(FSerializer &arc) { Super::Serialize (arc); - if (arc.IsStoring ()) - { - arc.WriteSprite (SpriteOverride); - } - else - { - SpriteOverride = arc.ReadSprite (); - } - arc << CurrentWeapon; + auto def = (AScriptedMarine*)GetDefault(); + + arc.Sprite("spriteoverride", SpriteOverride, &def->SpriteOverride) + ("currentweapon", CurrentWeapon, def->CurrentWeapon); } void AScriptedMarine::Activate (AActor *activator) diff --git a/src/g_game.cpp b/src/g_game.cpp index 3c9d274c80..5d1f83b828 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef __APPLE__ #include #endif @@ -78,12 +79,14 @@ #include "p_acs.h" #include "p_effect.h" #include "m_joy.h" -#include "farchive.h" #include "r_renderer.h" #include "r_utility.h" #include "a_morph.h" #include "p_spec.h" #include "r_data/colormaps.h" +#include "serializer.h" +#include "w_zip.h" +#include "resourcefiles/resourcefile.h" #include @@ -110,10 +113,11 @@ void G_DoWorldDone (void); void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description); void G_DoAutoSave (); -void STAT_Write(FILE *file); -void STAT_Read(PNGHandle *png); +void STAT_Serialize(FSerializer &file); +bool WriteZip(const char *filename, TArray &filenames, TArray &content); FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH); +CVAR(Bool, save_formatted, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // use formatted JSON for saves (more readable but a larger files and a bit slower. CVAR (Int, deathmatch, 0, CVAR_SERVERINFO|CVAR_LATCH); CVAR (Bool, chasedemo, false, 0); CVAR (Bool, storesavepic, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -1775,7 +1779,7 @@ void G_LoadGame (const char* name, bool hidecon) } } -static bool CheckSingleWad (char *name, bool &printRequires, bool printwarn) +static bool CheckSingleWad (const char *name, bool &printRequires, bool printwarn) { if (name == NULL) { @@ -1795,22 +1799,20 @@ static bool CheckSingleWad (char *name, bool &printRequires, bool printwarn) } } printRequires = true; - delete[] name; return false; } - delete[] name; return true; } // Return false if not all the needed wads have been loaded. -bool G_CheckSaveGameWads (PNGHandle *png, bool printwarn) +bool G_CheckSaveGameWads (FSerializer &arc, bool printwarn) { - char *text; bool printRequires = false; + FString text; - text = M_GetPNGText (png, "Game WAD"); + arc("Game WAD", text); CheckSingleWad (text, printRequires, printwarn); - text = M_GetPNGText (png, "Map WAD"); + arc("Map WAD", text); CheckSingleWad (text, printRequires, printwarn); if (printRequires) @@ -1828,9 +1830,6 @@ bool G_CheckSaveGameWads (PNGHandle *png, bool printwarn) void G_DoLoadGame () { - char sigcheck[20]; - char *text = NULL; - char *map; bool hidecon; if (gameaction != ga_autoloadgame) @@ -1840,151 +1839,168 @@ void G_DoLoadGame () hidecon = gameaction == ga_loadgamehidecon; gameaction = ga_nothing; - FILE *stdfile = fopen (savename.GetChars(), "rb"); - if (stdfile == NULL) + FResourceFile *resfile = FResourceFile::OpenResourceFile(savename.GetChars(), nullptr, true, true); + if (resfile == nullptr) { Printf ("Could not read savegame '%s'\n", savename.GetChars()); return; } - - PNGHandle *png = M_VerifyPNG (stdfile); - if (png == NULL) + try { - fclose (stdfile); - Printf ("'%s' is not a valid (PNG) savegame\n", savename.GetChars()); - return; - } - - SaveVersion = 0; - - // Check whether this savegame actually has been created by a compatible engine. - // Since there are ZDoom derivates using the exact same savegame format but - // with mutual incompatibilities this check simplifies things significantly. - char *engine = M_GetPNGText (png, "Engine"); - if (engine == NULL || 0 != strcmp (engine, GAMESIG)) - { - // Make a special case for the message printed for old savegames that don't - // have this information. - if (engine == NULL) + FResourceLump *info = resfile->FindLump("info.json"); + if (info == nullptr) { - Printf ("Savegame is from an incompatible version\n"); + delete resfile; + Printf("'%s' is not a valid savegame: Missing 'info.json'.\n", savename.GetChars()); + return; } - else + + SaveVersion = 0; + + void *data = info->CacheLump(); + FSerializer arc; + if (!arc.OpenReader((const char *)data, info->LumpSize)) { - Printf ("Savegame is from another ZDoom-based engine: %s\n", engine); - delete[] engine; + Printf("Failed to access savegame info\n"); + delete resfile; + return; } - delete png; - fclose (stdfile); - return; - } - if (engine != NULL) - { - delete[] engine; - } - SaveVersion = 0; - if (!M_GetPNGText (png, "ZDoom Save Version", sigcheck, 20) || - 0 != strncmp (sigcheck, SAVESIG, 9) || // ZDOOMSAVE is the first 9 chars - (SaveVersion = atoi (sigcheck+9)) < MINSAVEVER) - { - delete png; - fclose (stdfile); - Printf ("Savegame is from an incompatible version"); - if (SaveVersion != 0) + // Check whether this savegame actually has been created by a compatible engine. + // Since there are ZDoom derivates using the exact same savegame format but + // with mutual incompatibilities this check simplifies things significantly. + FString savever, engine, map; + arc("Save Version", SaveVersion); + arc("Engine", engine); + arc("Current Map", map); + + if (engine.CompareNoCase(GAMESIG) != 0) { - Printf(": %d (%d is the oldest supported)", SaveVersion, MINSAVEVER); + // Make a special case for the message printed for old savegames that don't + // have this information. + if (engine.IsEmpty()) + { + Printf("Savegame is from an incompatible version\n"); + } + else + { + Printf("Savegame is from another ZDoom-based engine: %s\n", engine); + } + delete resfile; + return; } - Printf("\n"); + + if (SaveVersion < MINSAVEVER || SaveVersion > SAVEVER) + { + delete resfile; + Printf("Savegame is from an incompatible version"); + if (SaveVersion < MINSAVEVER) + { + Printf(": %d (%d is the oldest supported)", SaveVersion, MINSAVEVER); + } + else + { + Printf(": %d (%d is the highest supported)", SaveVersion, SAVEVER); + } + Printf("\n"); + return; + } + + if (!G_CheckSaveGameWads(arc, true)) + { + delete resfile; + return; + } + + if (map.IsEmpty()) + { + Printf("Savegame is missing the current map\n"); + delete resfile; + return; + } + + // Now that it looks like we can load this save, hide the fullscreen console if it was up + // when the game was selected from the menu. + if (hidecon && gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + } + // we are done with info.json. + arc.Close(); + + info = resfile->FindLump("globals.json"); + if (info == nullptr) + { + delete resfile; + Printf("'%s' is not a valid savegame: Missing 'globals.json'.\n", savename.GetChars()); + return; + } + + data = info->CacheLump(); + if (!arc.OpenReader((const char *)data, info->LumpSize)) + { + Printf("Failed to access savegame info\n"); + delete resfile; + return; + } + + + // Read intermission data for hubs + G_SerializeHub(arc); + + bglobal.RemoveAllBots(true); + + FString cvar; + arc("importantcvars", cvar); + if (!cvar.IsEmpty()) + { + BYTE *vars_p = (BYTE *)cvar.GetChars(); + C_ReadCVars(&vars_p); + } + + DWORD time[2] = { 1,0 }; + + arc("ticrate", time[0]) + ("leveltime", time[1]); + // dearchive all the modifications + level.time = Scale(time[1], TICRATE, time[0]); + + G_ReadSnapshots(resfile); + delete resfile; // we no longer need the resource file below this point + resfile = nullptr; + G_ReadVisited(arc); + + // load a base level + savegamerestore = true; // Use the player actors in the savegame + bool demoplaybacksave = demoplayback; + G_InitNew(map, false); + demoplayback = demoplaybacksave; + savegamerestore = false; + + STAT_Serialize(arc); + FRandom::StaticReadRNGState(arc); + P_ReadACSDefereds(arc); + P_ReadACSVars(arc); + + NextSkill = -1; + arc("nextskill", NextSkill); + + if (level.info != nullptr) + level.info->Snapshot.Clean(); + + BackupSaveName = savename; + + // At this point, the GC threshold is likely a lot higher than the + // amount of memory in use, so bring it down now by starting a + // collection. + GC::StartCollection(); + } + catch (...) + { + // delete the resource file if anything goes wrong in here. + if (resfile != nullptr) delete resfile; return; } - - if (!G_CheckSaveGameWads (png, true)) - { - fclose (stdfile); - return; - } - - map = M_GetPNGText (png, "Current Map"); - if (map == NULL) - { - Printf ("Savegame is missing the current map\n"); - fclose (stdfile); - return; - } - - // Now that it looks like we can load this save, hide the fullscreen console if it was up - // when the game was selected from the menu. - if (hidecon && gamestate == GS_FULLCONSOLE) - { - gamestate = GS_HIDECONSOLE; - } - - // Read intermission data for hubs - G_ReadHubInfo(png); - - bglobal.RemoveAllBots (true); - - text = M_GetPNGText (png, "Important CVARs"); - if (text != NULL) - { - BYTE *vars_p = (BYTE *)text; - C_ReadCVars (&vars_p); - delete[] text; - } - - // dearchive all the modifications - if (M_FindPNGChunk (png, MAKE_ID('p','t','I','c')) == 8) - { - DWORD time[2]; - fread (&time, 8, 1, stdfile); - time[0] = BigLong((unsigned int)time[0]); - time[1] = BigLong((unsigned int)time[1]); - level.time = Scale (time[1], TICRATE, time[0]); - } - else - { // No ptIc chunk so we don't know how long the user was playing - level.time = 0; - } - - G_ReadSnapshots (png); - - // load a base level - savegamerestore = true; // Use the player actors in the savegame - bool demoplaybacksave = demoplayback; - G_InitNew (map, false); - demoplayback = demoplaybacksave; - delete[] map; - savegamerestore = false; - - STAT_Read(png); - FRandom::StaticReadRNGState(png); - P_ReadACSDefereds(png); - P_ReadACSVars(png); - - NextSkill = -1; - if (M_FindPNGChunk (png, MAKE_ID('s','n','X','t')) == 1) - { - BYTE next; - fread (&next, 1, 1, stdfile); - NextSkill = next; - } - - if (level.info->snapshot != NULL) - { - delete level.info->snapshot; - level.info->snapshot = NULL; - } - - BackupSaveName = savename; - - delete png; - fclose (stdfile); - - // At this point, the GC threshold is likely a lot higher than the - // amount of memory in use, so bring it down now by starting a - // collection. - GC::StartCollection(); } @@ -2047,7 +2063,7 @@ FString G_BuildSaveName (const char *prefix, int slot) name << prefix; if (slot >= 0) { - name.AppendFormat("%d.zds", slot); + name.AppendFormat("%d." SAVEGAME_EXT, slot); } return name; } @@ -2104,23 +2120,23 @@ void G_DoAutoSave () } -static void PutSaveWads (FILE *file) +static void PutSaveWads (FSerializer &arc) { const char *name; // Name of IWAD name = Wads.GetWadName (FWadCollection::IWAD_FILENUM); - M_AppendPNGText (file, "Game WAD", name); + arc.AddString("Game WAD", name); // Name of wad the map resides in if (Wads.GetLumpFile (level.lumpnum) > 1) { name = Wads.GetWadName (Wads.GetLumpFile (level.lumpnum)); - M_AppendPNGText (file, "Map WAD", name); + arc.AddString("Map WAD", name); } } -static void PutSaveComment (FILE *file) +static void PutSaveComment (FSerializer &arc) { char comment[256]; const char *readableTime; @@ -2135,7 +2151,7 @@ static void PutSaveComment (FILE *file) strncpy (comment+15, readableTime+10, 9); comment[24] = 0; - M_AppendPNGText (file, "Creation Time", comment); + arc.AddString("Creation Time", comment); // Get level name //strcpy (comment, level.level_name); @@ -2150,10 +2166,10 @@ static void PutSaveComment (FILE *file) comment[len+16] = 0; // Write out the comment - M_AppendPNGText (file, "Comment", comment); + arc.AddString("Comment", comment); } -static void PutSavePic (FILE *file, int width, int height) +static void PutSavePic (FileWriter *file, int width, int height) { if (width <= 0 || height <= 0 || !storesavepic) { @@ -2167,6 +2183,9 @@ static void PutSavePic (FILE *file, int width, int height) void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description) { + TArray savegame_content; + TArray savegame_filenames; + char buf[100]; // Do not even try, if we're not in a level. (Can happen after @@ -2178,7 +2197,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio if (demoplayback) { - filename = G_BuildSaveName ("demosave.zds", -1); + filename = G_BuildSaveName ("demosave." SAVEGAME_EXT, -1); } if (cl_waitforsave) @@ -2187,87 +2206,97 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio insave = true; G_SnapshotLevel (); - FILE *stdfile = fopen (filename, "wb"); + BufferWriter savepic; + FSerializer savegameinfo; // this is for displayable info about the savegame + FSerializer savegameglobals; // and this for non-level related info that must be saved. - if (stdfile == NULL) - { - Printf ("Could not create savegame '%s'\n", filename.GetChars()); - insave = false; - I_FreezeTime(false); - return; - } + savegameinfo.OpenWriter(true); + savegameglobals.OpenWriter(save_formatted); SaveVersion = SAVEVER; - PutSavePic (stdfile, SAVEPICWIDTH, SAVEPICHEIGHT); + PutSavePic(&savepic, SAVEPICWIDTH, SAVEPICHEIGHT); mysnprintf(buf, countof(buf), GAMENAME " %s", GetVersionString()); - M_AppendPNGText (stdfile, "Software", buf); - M_AppendPNGText (stdfile, "Engine", GAMESIG); - M_AppendPNGText (stdfile, "ZDoom Save Version", SAVESIG); - M_AppendPNGText (stdfile, "Title", description); - M_AppendPNGText (stdfile, "Current Map", level.MapName); - PutSaveWads (stdfile); - PutSaveComment (stdfile); + // put some basic info into the PNG so that this isn't lost when the image gets extracted. + M_AppendPNGText(&savepic, "Software", buf); + M_AppendPNGText(&savepic, "Title", description); + M_AppendPNGText(&savepic, "Current Map", level.MapName); + M_FinishPNG(&savepic); + + int ver = SAVEVER; + savegameinfo.AddString("Software", buf) + .AddString("Engine", GAMESIG) + ("Save Version", ver) + .AddString("Title", description) + .AddString("Current Map", level.MapName); + + + PutSaveWads (savegameinfo); + PutSaveComment (savegameinfo); // Intermission stats for hubs - G_WriteHubInfo(stdfile); + G_SerializeHub(savegameglobals); { FString vars = C_GetMassCVarString(CVAR_SERVERINFO); - M_AppendPNGText (stdfile, "Important CVARs", vars.GetChars()); + savegameglobals.AddString("importantcvars", vars.GetChars()); } if (level.time != 0 || level.maptime != 0) { - DWORD time[2] = { DWORD(BigLong(TICRATE)), DWORD(BigLong(level.time)) }; - M_AppendPNGChunk (stdfile, MAKE_ID('p','t','I','c'), (BYTE *)&time, 8); + int tic = TICRATE; + savegameglobals("ticrate", tic); + savegameglobals("leveltime", level.time); } - G_WriteSnapshots (stdfile); - STAT_Write(stdfile); - FRandom::StaticWriteRNGState (stdfile); - P_WriteACSDefereds (stdfile); + STAT_Serialize(savegameglobals); + FRandom::StaticWriteRNGState(savegameglobals); + P_WriteACSDefereds(savegameglobals); + P_WriteACSVars(savegameglobals); + G_WriteVisited(savegameglobals); - P_WriteACSVars(stdfile); if (NextSkill != -1) { - BYTE next = NextSkill; - M_AppendPNGChunk (stdfile, MAKE_ID('s','n','X','t'), &next, 1); + savegameglobals("nextskill", NextSkill); } - M_FinishPNG (stdfile); - fclose (stdfile); + auto picdata = savepic.GetBuffer(); + FCompressedBuffer bufpng = { picdata->Size(), picdata->Size(), METHOD_STORED, 0, crc32(0, &(*picdata)[0], picdata->Size()), (char*)&(*picdata)[0] }; + + savegame_content.Push(bufpng); + savegame_filenames.Push("savepic.png"); + savegame_content.Push(savegameinfo.GetCompressedOutput()); + savegame_filenames.Push("info.json"); + savegame_content.Push(savegameglobals.GetCompressedOutput()); + savegame_filenames.Push("globals.json"); + + G_WriteSnapshots (savegame_filenames, savegame_content); + + + WriteZip(filename, savegame_filenames, savegame_content); M_NotifyNewSave (filename.GetChars(), description, okForQuicksave); - // Check whether the file is ok. - bool success = false; - stdfile = fopen (filename.GetChars(), "rb"); - if (stdfile != NULL) - { - PNGHandle *pngh = M_VerifyPNG(stdfile); - if (pngh != NULL) - { - success = true; - delete pngh; - } - fclose(stdfile); - } - if (success) + // delete the JSON buffers we created just above. Everything else will + // either still be needed or taken care of automatically. + savegame_content[1].Clean(); + savegame_content[2].Clean(); + + // Check whether the file is ok by trying to open it. + FResourceFile *test = FResourceFile::OpenResourceFile(filename, nullptr, true); + if (test != nullptr) { + delete test; if (longsavemessages) Printf ("%s (%s)\n", GStrings("GGSAVED"), filename.GetChars()); else Printf ("%s\n", GStrings("GGSAVED")); } else Printf(PRINT_HIGH, "Save failed\n"); + BackupSaveName = filename; // We don't need the snapshot any longer. - if (level.info->snapshot != NULL) - { - delete level.info->snapshot; - level.info->snapshot = NULL; - } + level.info->Snapshot.Clean(); insave = false; I_FreezeTime(false); diff --git a/src/g_game.h b/src/g_game.h index bf40998540..0a11527fd7 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -24,7 +24,6 @@ #define __G_GAME__ struct event_t; -struct PNGHandle; // @@ -68,8 +67,8 @@ void G_ScreenShot (char *filename); FString G_BuildSaveName (const char *prefix, int slot); -struct PNGHandle; -bool G_CheckSaveGameWads (PNGHandle *png, bool printwarn); +class FSerializer; +bool G_CheckSaveGameWads (FSerializer &arc, bool printwarn); enum EFinishLevelType { diff --git a/src/g_heretic/a_dsparil.cpp b/src/g_heretic/a_dsparil.cpp index 124cb14034..f5870f09ec 100644 --- a/src/g_heretic/a_dsparil.cpp +++ b/src/g_heretic/a_dsparil.cpp @@ -148,7 +148,7 @@ void P_DSparilTeleport (AActor *actor) DSpotState *state = DSpotState::GetSpotState(); if (state == NULL) return; - spot = state->GetSpotWithMinMaxDistance(PClass::FindClass("BossSpot"), actor->X(), actor->Y(), 128, 0); + spot = state->GetSpotWithMinMaxDistance(PClass::FindActor("BossSpot"), actor->X(), actor->Y(), 128, 0); if (spot == NULL) return; prev = actor->Pos(); diff --git a/src/g_heretic/a_hereticmisc.cpp b/src/g_heretic/a_hereticmisc.cpp index 95c2ae6d0a..36969f6e7d 100644 --- a/src/g_heretic/a_hereticmisc.cpp +++ b/src/g_heretic/a_hereticmisc.cpp @@ -14,10 +14,10 @@ #include "templates.h" #include "r_data/r_translate.h" #include "doomstat.h" -#include "farchive.h" #include "d_player.h" #include "a_morph.h" #include "p_spec.h" +#include "serializer.h" // Include all the other Heretic stuff here to reduce compile time #include "a_chicken.cpp" diff --git a/src/g_heretic/a_hereticweaps.cpp b/src/g_heretic/a_hereticweaps.cpp index d20146339e..eb7d49983f 100644 --- a/src/g_heretic/a_hereticweaps.cpp +++ b/src/g_heretic/a_hereticweaps.cpp @@ -866,16 +866,18 @@ class ARainTracker : public AInventory { DECLARE_CLASS (ARainTracker, AInventory) public: - void Serialize (FArchive &arc); - AActor *Rain1, *Rain2; + + void Serialize(FSerializer &arc); + TObjPtr Rain1, Rain2; }; IMPLEMENT_CLASS (ARainTracker) -void ARainTracker::Serialize (FArchive &arc) +void ARainTracker::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Rain1 << Rain2; + arc("rain1", Rain1) + ("rain2", Rain2); } //---------------------------------------------------------------------------- @@ -1140,10 +1142,11 @@ class APhoenixRod : public AWeapon { DECLARE_CLASS (APhoenixRod, AWeapon) public: - void Serialize (FArchive &arc) + + void Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << FlameCount; + arc("flamecount", FlameCount); } int FlameCount; // for flamethrower duration }; diff --git a/src/g_hexen/a_clericholy.cpp b/src/g_hexen/a_clericholy.cpp index 749ad3dad2..b08ab3e74a 100644 --- a/src/g_hexen/a_clericholy.cpp +++ b/src/g_hexen/a_clericholy.cpp @@ -31,10 +31,11 @@ class ACWeapWraithverge : public AClericWeapon { DECLARE_CLASS (ACWeapWraithverge, AClericWeapon) public: - void Serialize (FArchive &arc) + + void Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << CHolyCount; + arc("cholycount", CHolyCount); } PalEntry GetBlend () { diff --git a/src/g_hexen/a_heresiarch.cpp b/src/g_hexen/a_heresiarch.cpp index 7784f08330..c96c4cc1f2 100644 --- a/src/g_hexen/a_heresiarch.cpp +++ b/src/g_hexen/a_heresiarch.cpp @@ -64,19 +64,21 @@ class AHeresiarch : public AActor { DECLARE_CLASS (AHeresiarch, AActor) public: - const PClass *StopBall; + PClassActor *StopBall; DAngle BallAngle; - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void Die (AActor *source, AActor *inflictor, int dmgflags); }; IMPLEMENT_CLASS (AHeresiarch) -void AHeresiarch::Serialize (FArchive &arc) +void AHeresiarch::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << StopBall << BallAngle; + arc("stopball", StopBall) + ("ballangle", BallAngle); } void AHeresiarch::Die (AActor *source, AActor *inflictor, int dmgflags) @@ -105,10 +107,13 @@ public: DAngle AngleOffset; DAngle OldAngle; - void Serialize (FArchive &arc) + + + void Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << AngleOffset << OldAngle; + arc("angleoffset", AngleOffset) + ("oldangle", OldAngle); } bool SpecialBlastHandling (AActor *source, double strength) diff --git a/src/g_hexen/a_hexenmisc.cpp b/src/g_hexen/a_hexenmisc.cpp index c1d41e762f..31a17c3a37 100644 --- a/src/g_hexen/a_hexenmisc.cpp +++ b/src/g_hexen/a_hexenmisc.cpp @@ -17,13 +17,13 @@ #include "p_terrain.h" #include "m_bbox.h" #include "ravenshared.h" -#include "farchive.h" #include "v_palette.h" #include "g_game.h" #include "p_blockmap.h" #include "r_utility.h" #include "p_maputl.h" #include "p_spec.h" +#include "serializer.h" // Include all the Hexen stuff here to reduce compile time #include "a_bats.cpp" diff --git a/src/g_hexen/a_magestaff.cpp b/src/g_hexen/a_magestaff.cpp index 1bd20715dd..d7b06d009e 100644 --- a/src/g_hexen/a_magestaff.cpp +++ b/src/g_hexen/a_magestaff.cpp @@ -29,10 +29,11 @@ class AMWeapBloodscourge : public AMageWeapon { DECLARE_CLASS (AMWeapBloodscourge, AMageWeapon) public: - void Serialize (FArchive &arc) + + void Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << MStaffCount; + arc("mstaffcount", MStaffCount); } PalEntry GetBlend () { diff --git a/src/g_hexen/a_spike.cpp b/src/g_hexen/a_spike.cpp index 4770f5d3a8..9f73963996 100644 --- a/src/g_hexen/a_spike.cpp +++ b/src/g_hexen/a_spike.cpp @@ -22,7 +22,8 @@ class AThrustFloor : public AActor DECLARE_CLASS (AThrustFloor, AActor) HAS_OBJECT_POINTERS public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void Activate (AActor *activator); void Deactivate (AActor *activator); @@ -34,10 +35,10 @@ IMPLEMENT_POINTY_CLASS (AThrustFloor) DECLARE_POINTER (DirtClump) END_POINTERS -void AThrustFloor::Serialize (FArchive &arc) +void AThrustFloor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << DirtClump; + arc("dirtclump", DirtClump); } void AThrustFloor::Activate (AActor *activator) diff --git a/src/g_hub.cpp b/src/g_hub.cpp index e436077104..cabcef2d3c 100644 --- a/src/g_hub.cpp +++ b/src/g_hub.cpp @@ -43,7 +43,7 @@ #include "m_png.h" #include "gstrings.h" #include "wi_stuff.h" -#include "farchive.h" +#include "serializer.h" //========================================================================== @@ -54,7 +54,7 @@ struct FHubInfo { - int finished_ep; + int levelnum; int maxkills; int maxitems; @@ -65,7 +65,7 @@ struct FHubInfo FHubInfo &operator=(const wbstartstruct_t &wbs) { - finished_ep = wbs.finished_ep; + levelnum = wbs.finished_ep; maxkills = wbs.maxkills; maxsecret= wbs.maxsecret; maxitems = wbs.maxitems; @@ -80,54 +80,54 @@ static TArray hubdata; void G_LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs) { - unsigned int i,j; + unsigned int i, j; if (cluster->flags & CLUSTER_HUB) { - for(i=0;imaxkills=wbs->maxitems=wbs->maxsecret=0; - for(i=0;imaxkills = wbs->maxitems = wbs->maxsecret = 0; + for (i = 0; i < MAXPLAYERS; i++) { - wbs->plyr[i].sitems=wbs->plyr[i].skills=wbs->plyr[i].ssecret=0; + wbs->plyr[i].sitems = wbs->plyr[i].skills = wbs->plyr[i].ssecret = 0; } - for(i=0;imaxkills += hubdata[i].maxkills; wbs->maxitems += hubdata[i].maxitems; wbs->maxsecret += hubdata[i].maxsecret; - for(j=0;jplyr[j].sitems += hubdata[i].plyr[j].sitems; wbs->plyr[j].skills += hubdata[i].plyr[j].skills; wbs->plyr[j].ssecret += hubdata[i].plyr[j].ssecret; } } - if (cluster->ClusterName.IsNotEmpty()) + if (cluster->ClusterName.IsNotEmpty()) { if (cluster->flags & CLUSTER_LOOKUPNAME) { @@ -140,7 +140,7 @@ void G_LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs) } } } - if (mode!=FINISH_SameHub) hubdata.Clear(); + if (mode != FINISH_SameHub) hubdata.Clear(); } //========================================================================== @@ -148,39 +148,41 @@ void G_LeavingHub(int mode, cluster_info_t * cluster, wbstartstruct_t * wbs) // Serialize intermission info for hubs // //========================================================================== -#define HUBS_ID MAKE_ID('h','u','B','s') -static void G_SerializeHub(FArchive & arc) +FSerializer &Serialize(FSerializer &arc, const char *key, wbplayerstruct_t &h, wbplayerstruct_t *def) { - int i=hubdata.Size(); - arc << i; - if (i>0) + if (arc.BeginObject(key)) { - if (arc.IsStoring()) arc.Write(&hubdata[0], i * sizeof(FHubInfo)); - else - { - hubdata.Resize(i); - arc.Read(&hubdata[0], i * sizeof(FHubInfo)); - } + arc("in", h.in) + ("kills", h.skills) + ("items", h.sitems) + ("secrets", h.ssecret) + ("time", h.stime) + ("fragcount", h.fragcount) + .Array("frags", h.frags, MAXPLAYERS) + .EndObject(); } - else hubdata.Clear(); + return arc; } -void G_WriteHubInfo (FILE *file) +FSerializer &Serialize(FSerializer &arc, const char *key, FHubInfo &h, FHubInfo *def) { - FPNGChunkArchive arc(file, HUBS_ID); - G_SerializeHub(arc); + if (arc.BeginObject(key)) + { + arc("levelnum", h.levelnum) + ("maxkills", h.maxkills) + ("maxitems", h.maxitems) + ("maxsecret", h.maxsecret) + ("maxfrags", h.maxfrags) + .Array("players", h.plyr, MAXPLAYERS) + .EndObject(); + } + return arc; } -void G_ReadHubInfo (PNGHandle *png) +void G_SerializeHub(FSerializer &arc) { - int chunklen; - - if ((chunklen = M_FindPNGChunk (png, HUBS_ID)) != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), HUBS_ID, chunklen); - G_SerializeHub(arc); - } + arc("hubinfo", hubdata); } void G_ClearHubInfo() diff --git a/src/g_hub.h b/src/g_hub.h index 8f148479c0..372546b833 100644 --- a/src/g_hub.h +++ b/src/g_hub.h @@ -1,14 +1,11 @@ #ifndef __G_HUB_H #define __G_HUB_H -#include - -struct PNGHandle; struct cluster_info_t; struct wbstartstruct_t; +class FSerializer; -void G_WriteHubInfo (FILE *file); -void G_ReadHubInfo (PNGHandle *png); +void G_SerializeHub (FSerializer &file); void G_LeavingHub(int mode, cluster_info_t * cluster, struct wbstartstruct_t * wbs); #endif diff --git a/src/g_level.cpp b/src/g_level.cpp index 4280a1fe55..c854fa8497 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -81,10 +81,10 @@ #include "a_sharedglobal.h" #include "a_strifeglobal.h" #include "r_data/colormaps.h" -#include "farchive.h" #include "r_renderer.h" #include "r_utility.h" #include "p_spec.h" +#include "serializer.h" #include "gi.h" @@ -93,7 +93,7 @@ void STAT_StartNewGame(const char *lev); void STAT_ChangeLevel(const char *newl); - +EXTERN_CVAR(Bool, save_formatted) EXTERN_CVAR (Float, sv_gravity) EXTERN_CVAR (Float, sv_aircontrol) EXTERN_CVAR (Int, disableautosave) @@ -822,7 +822,7 @@ void G_DoCompleted (void) } else { // Make sure we don't have a snapshot lying around from before. - level.info->ClearSnapshot(); + level.info->Snapshot.Clean(); } } else @@ -1477,135 +1477,6 @@ void G_AirControlChanged () } } -//========================================================================== -// -// -//========================================================================== -void gl_SerializeGlobals(FArchive &arc); - -void G_SerializeLevel (FArchive &arc, bool hubLoad) -{ - int i = level.totaltime; - - Renderer->StartSerialize(arc); - if (arc.IsLoading()) P_DestroyThinkers(hubLoad); - gl_SerializeGlobals(arc); - - arc << level.flags - << level.flags2 - << level.fadeto - << level.found_secrets - << level.found_items - << level.killed_monsters - << level.gravity - << level.aircontrol - << level.teamdamage - << level.maptime - << i; - - // Hub transitions must keep the current total time - if (!hubLoad) - level.totaltime = i; - - arc << level.skytexture1 << level.skytexture2; - if (arc.IsLoading()) - { - sky1texture = level.skytexture1; - sky2texture = level.skytexture2; - R_InitSkyMap(); - } - - G_AirControlChanged (); - - BYTE t; - - // Does this level have scrollers? - if (arc.IsStoring ()) - { - t = level.Scrolls ? 1 : 0; - arc << t; - } - else - { - arc << t; - if (level.Scrolls) - { - delete[] level.Scrolls; - level.Scrolls = NULL; - } - if (t) - { - level.Scrolls = new FSectorScrollValues[numsectors]; - memset (level.Scrolls, 0, sizeof(level.Scrolls)*numsectors); - } - } - - FBehavior::StaticSerializeModuleStates (arc); - if (arc.IsLoading()) interpolator.ClearInterpolations(); - P_SerializeWorld(arc); - P_SerializeThinkers (arc, hubLoad); - P_SerializeWorldActors(arc); // serializing actor pointers in the world data must be done after SerializeWorld has restored the entire sector state, otherwise LinkToWorld may fail. - P_SerializePolyobjs (arc); - P_SerializeSubsectors(arc); - StatusBar->Serialize (arc); - - arc << level.total_monsters << level.total_items << level.total_secrets; - - // Does this level have custom translations? - FRemapTable *trans; - WORD w; - if (arc.IsStoring ()) - { - for (unsigned int i = 0; i < translationtables[TRANSLATION_LevelScripted].Size(); ++i) - { - trans = translationtables[TRANSLATION_LevelScripted][i]; - if (trans != NULL && !trans->IsIdentity()) - { - w = WORD(i); - arc << w; - trans->Serialize(arc); - } - } - w = 0xffff; - arc << w; - } - else - { - while (arc << w, w != 0xffff) - { - trans = translationtables[TRANSLATION_LevelScripted].GetVal(w); - if (trans == NULL) - { - trans = new FRemapTable; - translationtables[TRANSLATION_LevelScripted].SetVal(w, trans); - } - trans->Serialize(arc); - } - } - - // This must be saved, too, of course! - FCanvasTextureInfo::Serialize (arc); - AM_SerializeMarkers(arc); - - P_SerializePlayers (arc, hubLoad); - P_SerializeSounds (arc); - if (arc.IsLoading()) - { - for (i = 0; i < numsectors; i++) - { - P_Recalculate3DFloors(§ors[i]); - } - for (i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i] && players[i].mo != NULL) - { - players[i].mo->SetupWeaponSlots(); - } - } - } - Renderer->EndSerialize(arc); -} - //========================================================================== // // Archives the current level @@ -1614,19 +1485,18 @@ void G_SerializeLevel (FArchive &arc, bool hubLoad) void G_SnapshotLevel () { - if (level.info->snapshot) - delete level.info->snapshot; + level.info->Snapshot.Clean(); if (level.info->isValid()) { - level.info->snapshotVer = SAVEVER; - level.info->snapshot = new FCompressedMemFile; - level.info->snapshot->Open (); + FSerializer arc; - FArchive arc (*level.info->snapshot); - - SaveVersion = SAVEVER; - G_SerializeLevel (arc, false); + if (arc.OpenWriter(save_formatted)) + { + SaveVersion = SAVEVER; + G_SerializeLevel(arc, false); + level.info->Snapshot = arc.GetCompressedOutput(); + } } } @@ -1639,18 +1509,15 @@ void G_SnapshotLevel () void G_UnSnapshotLevel (bool hubLoad) { - if (level.info->snapshot == NULL) + if (level.info->Snapshot.mBuffer == nullptr) return; if (level.info->isValid()) { - SaveVersion = level.info->snapshotVer; - level.info->snapshot->Reopen (); - FArchive arc (*level.info->snapshot); - if (hubLoad) - arc.SetHubTravel (); + FSerializer arc; + if (!arc.OpenReader(&level.info->Snapshot)) return; + G_SerializeLevel (arc, hubLoad); - arc.Close (); level.FromSnapshot = true; TThinkerIterator it; @@ -1680,7 +1547,7 @@ void G_UnSnapshotLevel (bool hubLoad) } } // No reason to keep the snapshot around once the level's been entered. - level.info->ClearSnapshot(); + level.info->Snapshot.Clean(); if (hubLoad) { // Unlock ACS global strings that were locked when the snapshot was made. @@ -1693,10 +1560,28 @@ void G_UnSnapshotLevel (bool hubLoad) // //========================================================================== -static void writeSnapShot (FArchive &arc, level_info_t *i) +void G_WriteSnapshots(TArray &filenames, TArray &buffers) { - arc << i->snapshotVer << i->MapName; - i->snapshot->Serialize (arc); + unsigned int i; + FString filename; + + for (i = 0; i < wadlevelinfos.Size(); i++) + { + if (wadlevelinfos[i].Snapshot.mCompressedSize > 0) + { + filename.Format("%s.map.json", wadlevelinfos[i].MapName.GetChars()); + filename.ToLower(); + filenames.Push(filename); + buffers.Push(wadlevelinfos[i].Snapshot); + } + } + if (TheDefaultLevelInfo.Snapshot.mCompressedSize > 0) + { + filename.Format("%s.mapd.json", TheDefaultLevelInfo.MapName.GetChars()); + filename.ToLower(); + filenames.Push(filename); + buffers.Push(TheDefaultLevelInfo.Snapshot); + } } //========================================================================== @@ -1704,70 +1589,39 @@ static void writeSnapShot (FArchive &arc, level_info_t *i) // //========================================================================== -void G_WriteSnapshots (FILE *file) +void G_WriteVisited(FSerializer &arc) { - unsigned int i; - - for (i = 0; i < wadlevelinfos.Size(); i++) + if (arc.BeginArray("visited")) { - if (wadlevelinfos[i].snapshot) + // Write out which levels have been visited + for (auto & wi : wadlevelinfos) { - FPNGChunkArchive arc (file, SNAP_ID); - writeSnapShot (arc, (level_info_t *)&wadlevelinfos[i]); - } - } - if (TheDefaultLevelInfo.snapshot != NULL) - { - FPNGChunkArchive arc (file, DSNP_ID); - writeSnapShot(arc, &TheDefaultLevelInfo); - } - - FPNGChunkArchive *arc = NULL; - - // Write out which levels have been visited - for (i = 0; i < wadlevelinfos.Size(); ++i) - { - if (wadlevelinfos[i].flags & LEVEL_VISITED) - { - if (arc == NULL) + if (wi.flags & LEVEL_VISITED) { - arc = new FPNGChunkArchive (file, VIST_ID); + arc.AddString(nullptr, wi.MapName); } - (*arc) << wadlevelinfos[i].MapName; } - } - - if (arc != NULL) - { - FString empty = ""; - (*arc) << empty; - delete arc; + arc.EndArray(); } // Store player classes to be used when spawning a random class if (multiplayer) { - FPNGChunkArchive arc2 (file, RCLS_ID); - for (i = 0; i < MAXPLAYERS; ++i) - { - SBYTE cnum = SinglePlayerClass[i]; - arc2 << cnum; - } + arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); } - // Store player classes that are currently in use - FPNGChunkArchive arc3 (file, PCLS_ID); - for (i = 0; i < MAXPLAYERS; ++i) + if (arc.BeginObject("playerclasses")) { - BYTE pnum; - if (playeringame[i]) + for (int i = 0; i < MAXPLAYERS; ++i) { - pnum = i; - arc3 << pnum; - arc3.UserWriteClass (players[i].cls); + if (playeringame[i]) + { + FString key; + key.Format("%d", i); + arc(key, players[i].cls); + } } - pnum = 255; - arc3 << pnum; + arc.EndObject(); } } @@ -1776,95 +1630,89 @@ void G_WriteSnapshots (FILE *file) // //========================================================================== -void G_ReadSnapshots (PNGHandle *png) +void G_ReadSnapshots(FResourceFile *resf) { - DWORD chunkLen; FString MapName; level_info_t *i; - G_ClearSnapshots (); + G_ClearSnapshots(); - chunkLen = (DWORD)M_FindPNGChunk (png, SNAP_ID); - while (chunkLen != 0) + for (unsigned j = 0; j < resf->LumpCount(); j++) { - FPNGChunkArchive arc (png->File->GetFile(), SNAP_ID, chunkLen); - DWORD snapver; - - arc << snapver; - arc << MapName; - i = FindLevelInfo (MapName); - i->snapshotVer = snapver; - i->snapshot = new FCompressedMemFile; - i->snapshot->Serialize (arc); - chunkLen = (DWORD)M_NextPNGChunk (png, SNAP_ID); - } - - chunkLen = (DWORD)M_FindPNGChunk (png, DSNP_ID); - if (chunkLen != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), DSNP_ID, chunkLen); - DWORD snapver; - - arc << snapver; - arc << MapName; - TheDefaultLevelInfo.snapshotVer = snapver; - TheDefaultLevelInfo.snapshot = new FCompressedMemFile; - TheDefaultLevelInfo.snapshot->Serialize (arc); - } - - chunkLen = (DWORD)M_FindPNGChunk (png, VIST_ID); - if (chunkLen != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), VIST_ID, chunkLen); - - while (arc << MapName, MapName.Len() > 0) + FResourceLump * resl = resf->GetLump(j); + if (resl != nullptr) { - i = FindLevelInfo(MapName); - i->flags |= LEVEL_VISITED; + auto ptr = strstr(resl->FullName, ".map.json"); + if (ptr != nullptr) + { + ptrdiff_t maplen = ptr - resl->FullName.GetChars(); + FString mapname(resl->FullName.GetChars(), (size_t)maplen); + i = FindLevelInfo(mapname); + if (i != nullptr) + { + i->Snapshot = resl->GetRawData(); + } + } + else + { + auto ptr = strstr(resl->FullName, ".mapd.json"); + if (ptr != nullptr) + { + ptrdiff_t maplen = ptr - resl->FullName.GetChars(); + FString mapname(resl->FullName.GetChars(), (size_t)maplen); + TheDefaultLevelInfo.Snapshot = resl->GetRawData(); + } + } } } - - chunkLen = (DWORD)M_FindPNGChunk (png, RCLS_ID); - if (chunkLen != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), PCLS_ID, chunkLen); - SBYTE cnum; - - for (DWORD j = 0; j < chunkLen; ++j) - { - arc << cnum; - SinglePlayerClass[j] = cnum; - } - } - - chunkLen = (DWORD)M_FindPNGChunk (png, PCLS_ID); - if (chunkLen != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), RCLS_ID, chunkLen); - BYTE pnum; - - arc << pnum; - while (pnum != 255) - { - arc.UserReadClass (players[pnum].cls); - arc << pnum; - } - } - png->File->ResetFilePtr(); } +//========================================================================== +// +// +//========================================================================== + +void G_ReadVisited(FSerializer &arc) +{ + if (arc.BeginArray("visited")) + { + for (int s = arc.ArraySize(); s > 0; s--) + { + FString str; + arc(nullptr, str); + auto i = FindLevelInfo(str); + if (i != nullptr) i->flags |= LEVEL_VISITED; + } + arc.EndArray(); + } + + arc.Array("randomclasses", SinglePlayerClass, MAXPLAYERS); + + if (arc.BeginObject("playerclasses")) + { + for (int i = 0; i < MAXPLAYERS; ++i) + { + FString key; + key.Format("%d", i); + arc(key, players[i].cls); + } + arc.EndObject(); + } +} + +//========================================================================== +// +// //========================================================================== CCMD(listsnapshots) { for (unsigned i = 0; i < wadlevelinfos.Size(); ++i) { - FCompressedMemFile *snapshot = wadlevelinfos[i].snapshot; - if (snapshot != NULL) + FCompressedBuffer *snapshot = &wadlevelinfos[i].Snapshot; + if (snapshot->mBuffer != nullptr) { - unsigned int comp, uncomp; - snapshot->GetSizes(comp, uncomp); - Printf("%s (%u -> %u bytes)\n", wadlevelinfos[i].MapName.GetChars(), comp, uncomp); + Printf("%s (%u -> %u bytes)\n", wadlevelinfos[i].MapName.GetChars(), snapshot->mCompressedSize, snapshot->mSize); } } } @@ -1874,38 +1722,32 @@ CCMD(listsnapshots) // //========================================================================== -static void writeDefereds (FArchive &arc, level_info_t *i) +void P_WriteACSDefereds (FSerializer &arc) { - arc << i->MapName << i->defered; -} + bool found = false; -//========================================================================== -// -// -//========================================================================== - -void P_WriteACSDefereds (FILE *file) -{ - FPNGChunkArchive *arc = NULL; - - for (unsigned int i = 0; i < wadlevelinfos.Size(); i++) + // only write this stuff if needed + for (auto &wi : wadlevelinfos) { - if (wadlevelinfos[i].defered) + if (wi.deferred.Size() > 0) { - if (arc == NULL) + found = true; + break; + } + } + if (found && arc.BeginObject("deferred")) + { + for (auto &wi : wadlevelinfos) + { + if (wi.deferred.Size() > 0) { - arc = new FPNGChunkArchive (file, ACSD_ID); + if (wi.deferred.Size() > 0) + { + arc(wi.MapName, wi.deferred); + } } - writeDefereds (*arc, (level_info_t *)&wadlevelinfos[i]); } - } - - if (arc != NULL) - { - // Signal end of defereds - FString empty = ""; - (*arc) << empty; - delete arc; + arc.EndObject(); } } @@ -1914,28 +1756,27 @@ void P_WriteACSDefereds (FILE *file) // //========================================================================== -void P_ReadACSDefereds (PNGHandle *png) +void P_ReadACSDefereds (FSerializer &arc) { FString MapName; - size_t chunklen; - + P_RemoveDefereds (); - if ((chunklen = M_FindPNGChunk (png, ACSD_ID)) != 0) + if (arc.BeginObject("deferred")) { - FPNGChunkArchive arc (png->File->GetFile(), ACSD_ID, chunklen); + const char *key; - while (arc << MapName, MapName.Len() > 0) + while ((key = arc.GetKey())) { - level_info_t *i = FindLevelInfo(MapName); + level_info_t *i = FindLevelInfo(key); if (i == NULL) { - I_Error("Unknown map '%s' in savegame", MapName.GetChars()); + I_Error("Unknown map '%s' in savegame", key); } - arc << i->defered; + arc(nullptr, i->deferred); } + arc.EndObject(); } - png->File->ResetFilePtr(); } @@ -1947,9 +1788,9 @@ void P_ReadACSDefereds (PNGHandle *png) void FLevelLocals::Tick () { // Reset carry sectors - if (Scrolls != NULL) + if (Scrolls.Size() > 0) { - memset (Scrolls, 0, sizeof(*Scrolls)*numsectors); + memset (&Scrolls[0], 0, sizeof(Scrolls[0])*Scrolls.Size()); } } @@ -1964,10 +1805,10 @@ void FLevelLocals::AddScroller (int secnum) { return; } - if (Scrolls == NULL) + if (Scrolls.Size() == 0) { - Scrolls = new FSectorScrollValues[numsectors]; - memset (Scrolls, 0, sizeof(*Scrolls)*numsectors); + Scrolls.Resize(numsectors); + memset (&Scrolls[0], 0, sizeof(Scrolls[0])*numsectors); } } diff --git a/src/g_level.h b/src/g_level.h index 8360bd45ef..9f8c5efd52 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -38,7 +38,9 @@ #include "doomdef.h" #include "sc_man.h" #include "s_sound.h" +#include "p_acs.h" #include "textures/textures.h" +#include "resourcefiles/file_zip.h" struct level_info_t; struct cluster_info_t; @@ -223,8 +225,6 @@ enum ELevelFlags : unsigned int }; -struct acsdefered_t; - struct FSpecialAction { FName Type; // this is initialized before the actors... @@ -232,7 +232,6 @@ struct FSpecialAction int Args[5]; // must allow 16 bit tags for 666 & 667! }; -class FCompressedMemFile; class DScroller; class FScanner; @@ -295,9 +294,8 @@ struct level_info_t FString LevelName; SBYTE WallVertLight, WallHorizLight; int musicorder; - FCompressedMemFile *snapshot; - DWORD snapshotVer; - struct acsdefered_t *defered; + FCompressedBuffer Snapshot; + TArray deferred; float skyspeed1; float skyspeed2; DWORD fadeto; @@ -346,14 +344,16 @@ struct level_info_t } ~level_info_t() { - ClearSnapshot(); + Snapshot.Clean(); ClearDefered(); } void Reset(); bool isValid(); FString LookupLevelName (); - void ClearSnapshot(); - void ClearDefered(); + void ClearDefered() + { + deferred.Clear(); + } level_info_t *CheckLevelRedirect (); template @@ -375,17 +375,12 @@ struct level_info_t } }; -// [RH] These get zeroed every tic and are updated by thinkers. -struct FSectorScrollValues -{ - DVector2 Scroll; -}; - struct FLevelLocals { void Tick (); void AddScroller (int secnum); + BYTE md5[16]; // for savegame validation. If the MD5 does not match the savegame won't be loaded. int time; // time in the hub int maptime; // time in the map int totaltime; // time in the game @@ -436,7 +431,7 @@ struct FLevelLocals int airsupply; int DefaultEnvironment; // Default sound environment. - FSectorScrollValues *Scrolls; // NULL if no DScrollers in this level + TArray Scrolls; // NULL if no DScrollers in this level SBYTE WallVertLight; // Light diffs for vert/horiz walls SBYTE WallHorizLight; @@ -537,9 +532,10 @@ void G_ClearSnapshots (void); void P_RemoveDefereds (); void G_SnapshotLevel (void); void G_UnSnapshotLevel (bool keepPlayers); -struct PNGHandle; -void G_ReadSnapshots (PNGHandle *png); -void G_WriteSnapshots (FILE *file); +void G_ReadSnapshots (FResourceFile *); +void G_WriteSnapshots (TArray &, TArray &); +void G_WriteVisited(FSerializer &arc); +void G_ReadVisited(FSerializer &arc); void G_ClearHubInfo(); enum ESkillProperty diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index ac1552b999..bc04704740 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -45,7 +45,6 @@ #include "i_system.h" #include "gi.h" #include "gstrings.h" -#include "farchive.h" #include "p_acs.h" #include "doomstat.h" #include "d_player.h" @@ -196,7 +195,7 @@ void G_ClearSnapshots (void) { for (unsigned int i = 0; i < wadlevelinfos.Size(); i++) { - wadlevelinfos[i].ClearSnapshot(); + wadlevelinfos[i].Snapshot.Clean(); } // Since strings are only locked when snapshotting a level, unlock them // all now, since we got rid of all the snapshots that cared about them. @@ -248,9 +247,8 @@ void level_info_t::Reset() WallVertLight = +8; F1Pic = ""; musicorder = 0; - snapshot = NULL; - snapshotVer = 0; - defered = 0; + Snapshot = { 0,0,0,0,0,nullptr }; + deferred.Clear(); skyspeed1 = skyspeed2 = 0.f; fadeto = 0; outsidefog = 0xff000000; @@ -334,34 +332,6 @@ FString level_info_t::LookupLevelName() } -//========================================================================== -// -// -//========================================================================== - -void level_info_t::ClearSnapshot() -{ - if (snapshot != NULL) delete snapshot; - snapshot = NULL; -} - -//========================================================================== -// -// -//========================================================================== - -void level_info_t::ClearDefered() -{ - acsdefered_t *def = defered; - while (def) - { - acsdefered_t *next = def->next; - delete def; - def = next; - } - defered = NULL; -} - //========================================================================== // // diff --git a/src/g_raven/a_minotaur.cpp b/src/g_raven/a_minotaur.cpp index 34ecfd1617..9f58ce4c3f 100644 --- a/src/g_raven/a_minotaur.cpp +++ b/src/g_raven/a_minotaur.cpp @@ -11,9 +11,9 @@ #include "thingdef/thingdef.h" #include "g_level.h" #include "doomstat.h" -#include "farchive.h" #include "a_pickups.h" #include "d_player.h" +#include "serializer.h" #define MAULATORTICS (25*35) @@ -79,10 +79,10 @@ void AMinotaurFriend::BeginPlay () StartTime = -1; } -void AMinotaurFriend::Serialize (FArchive &arc) +void AMinotaurFriend::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << StartTime; + arc("starttime", StartTime); } void AMinotaurFriend::Die (AActor *source, AActor *inflictor, int dmgflags) diff --git a/src/g_raven/ravenshared.h b/src/g_raven/ravenshared.h index 0010905cce..81824a9577 100644 --- a/src/g_raven/ravenshared.h +++ b/src/g_raven/ravenshared.h @@ -23,7 +23,8 @@ public: void Die (AActor *source, AActor *inflictor, int dmgflags); bool OkayToSwitchTarget (AActor *other); void BeginPlay (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); }; #endif //__RAVENSHARED_H__ diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 0e333a46e6..c0ec37d09b 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -11,7 +11,7 @@ #include "p_enemy.h" #include "statnums.h" #include "templates.h" -#include "farchive.h" +#include "serializer.h" #include "r_data/r_translate.h" static FRandom pr_freezedeath ("FreezeDeath"); @@ -361,7 +361,7 @@ class DCorpsePointer : public DThinker public: DCorpsePointer (AActor *ptr); void Destroy (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); TObjPtr Corpse; DWORD Count; // Only the first corpse pointer's count is valid. private: @@ -435,10 +435,11 @@ void DCorpsePointer::Destroy () Super::Destroy (); } -void DCorpsePointer::Serialize (FArchive &arc) +void DCorpsePointer::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << Corpse << Count; + arc("corpse", Corpse) + ("count", Count); } diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index 2b783e380c..50472e51d0 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -6,7 +6,7 @@ #include "templates.h" #include "g_level.h" #include "d_player.h" -#include "farchive.h" +#include "serializer.h" IMPLEMENT_CLASS (AArmor) @@ -21,10 +21,17 @@ IMPLEMENT_CLASS (AHexenArmor) // //=========================================================================== -void ABasicArmor::Serialize (FArchive &arc) +void ABasicArmor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << SavePercent << BonusCount << MaxAbsorb << MaxFullAbsorb << AbsorbCount << ArmorType << ActualSaveAmount; + auto def = (ABasicArmor *)GetDefault(); + arc("savepercent", SavePercent, def->SavePercent) + ("bonuscount", BonusCount, def->BonusCount) + ("maxabsorb", MaxAbsorb, def->MaxAbsorb) + ("maxfullabsorb", MaxFullAbsorb, def->MaxFullAbsorb) + ("absorbcount", AbsorbCount, def->AbsorbCount) + ("armortype", ArmorType, def->ArmorType) + ("actualsaveamount", ActualSaveAmount, def->ActualSaveAmount); } //=========================================================================== @@ -192,11 +199,15 @@ void ABasicArmor::AbsorbDamage (int damage, FName damageType, int &newdamage) // //=========================================================================== -void ABasicArmorPickup::Serialize (FArchive &arc) +void ABasicArmorPickup::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << SavePercent << SaveAmount << MaxAbsorb << MaxFullAbsorb; - arc << DropTime; + + auto def = (ABasicArmorPickup *)GetDefault(); + arc("savepercent", SavePercent, def->SavePercent) + ("saveamount", SaveAmount, def->SaveAmount) + ("maxabsorb", MaxAbsorb, def->MaxAbsorb) + ("maxfullabsorb", MaxFullAbsorb, def->MaxFullAbsorb); } //=========================================================================== @@ -274,11 +285,17 @@ bool ABasicArmorPickup::Use (bool pickup) // //=========================================================================== -void ABasicArmorBonus::Serialize (FArchive &arc) +void ABasicArmorBonus::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << SavePercent << SaveAmount << MaxSaveAmount << BonusCount << BonusMax - << MaxAbsorb << MaxFullAbsorb; + auto def = (ABasicArmorBonus *)GetDefault(); + arc("savepercent", SavePercent, def->SavePercent) + ("saveamount", SaveAmount, def->SaveAmount) + ("maxsaveamount", MaxSaveAmount, def->MaxSaveAmount) + ("bonuscount", BonusCount, def->BonusCount) + ("bonusmax", BonusMax, def->BonusMax) + ("maxabsorb", MaxAbsorb, def->MaxAbsorb) + ("maxfullabsorb", MaxFullAbsorb, def->MaxFullAbsorb); } //=========================================================================== @@ -371,13 +388,12 @@ bool ABasicArmorBonus::Use (bool pickup) // //=========================================================================== -void AHexenArmor::Serialize (FArchive &arc) +void AHexenArmor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Slots[0] << Slots[1] << Slots[2] << Slots[3] - << Slots[4] - << SlotsIncrement[0] << SlotsIncrement[1] << SlotsIncrement[2] - << SlotsIncrement[3]; + auto def = (AHexenArmor *)GetDefault(); + arc.Array("slots", Slots, def->Slots, 5, true) + .Array("slotsincrement", SlotsIncrement, def->SlotsIncrement, 4); } //=========================================================================== diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp index 847bae596b..2a3746e750 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -19,7 +19,7 @@ #include "g_level.h" #include "doomstat.h" #include "v_palette.h" -#include "farchive.h" +#include "serializer.h" #include "r_utility.h" #include "r_data/colormaps.h" @@ -101,12 +101,15 @@ bool APowerupGiver::Use (bool pickup) // //=========================================================================== -void APowerupGiver::Serialize (FArchive &arc) +void APowerupGiver::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << PowerupType; - arc << EffectTics << BlendColor << Mode; - arc << Strength; + auto def = (APowerupGiver*)GetDefault(); + arc("poweruptype", PowerupType, def->PowerupType) + ("effecttics", EffectTics, def->EffectTics) + ("blendcolor", BlendColor, def->BlendColor) + ("mode", Mode, def->Mode) + ("strength", Strength, def->Strength); } // Powerup ------------------------------------------------------------------- @@ -136,11 +139,14 @@ void APowerup::Tick () // //=========================================================================== -void APowerup::Serialize (FArchive &arc) +void APowerup::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << EffectTics << BlendColor << Mode; - arc << Strength; + auto def = (APowerup*)GetDefault(); + arc("effecttics", EffectTics, def->EffectTics) + ("blendcolor", BlendColor, def->BlendColor) + ("mode", Mode, def->Mode) + ("strength", Strength, def->Strength); } //=========================================================================== @@ -902,10 +908,11 @@ IMPLEMENT_CLASS (APowerTorch) // //=========================================================================== -void APowerTorch::Serialize (FArchive &arc) +void APowerTorch::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << NewTorch << NewTorchDelta; + arc("newtorch", NewTorch) + ("newtorchdelta", NewTorchDelta); } //=========================================================================== @@ -964,10 +971,10 @@ IMPLEMENT_CLASS (APowerFlight) // //=========================================================================== -void APowerFlight::Serialize (FArchive &arc) +void APowerFlight::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << HitCenterFrame; + arc("hitcenterframe", HitCenterFrame); } //=========================================================================== @@ -1218,10 +1225,10 @@ IMPLEMENT_CLASS (APowerSpeed) // //=========================================================================== -void APowerSpeed::Serialize(FArchive &arc) +void APowerSpeed::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << SpeedFlags; + arc("speedflags", SpeedFlags); } //=========================================================================== @@ -1900,11 +1907,14 @@ IMPLEMENT_CLASS(APowerMorph) // //=========================================================================== -void APowerMorph::Serialize (FArchive &arc) +void APowerMorph::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << PlayerClass << MorphStyle << MorphFlash << UnMorphFlash; - arc << Player; + arc("playerclass", PlayerClass) + ("morphstyle", MorphStyle) + ("morphflash", MorphFlash) + ("unmorphflash", UnMorphFlash) + ("player", Player); } //=========================================================================== diff --git a/src/g_shared/a_artifacts.h b/src/g_shared/a_artifacts.h index 2a52756ab7..85ebc2e81b 100644 --- a/src/g_shared/a_artifacts.h +++ b/src/g_shared/a_artifacts.h @@ -16,7 +16,8 @@ public: virtual bool HandlePickup (AInventory *item); virtual AInventory *CreateCopy (AActor *other); virtual AInventory *CreateTossable (); - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual void OwnerDied (); virtual bool GetNoTeleportFreeze(); virtual PalEntry GetBlend (); @@ -51,7 +52,8 @@ class APowerupGiver : public AInventory DECLARE_CLASS_WITH_META (APowerupGiver, AInventory, PClassPowerupGiver) public: virtual bool Use (bool pickup); - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); PClassActor *PowerupType; @@ -121,7 +123,8 @@ class APowerTorch : public APowerLightAmp { DECLARE_CLASS (APowerTorch, APowerLightAmp) public: - void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); protected: void DoEffect (); int NewTorch, NewTorchDelta; @@ -132,7 +135,8 @@ class APowerFlight : public APowerup DECLARE_CLASS (APowerFlight, APowerup) public: bool DrawPowerup (int x, int y); - void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); protected: void InitEffect (); @@ -155,7 +159,8 @@ class APowerSpeed : public APowerup DECLARE_CLASS (APowerSpeed, APowerup) protected: void DoEffect (); - void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); double GetSpeedFactor(); public: int SpeedFlags; @@ -272,7 +277,8 @@ class APowerMorph : public APowerup { DECLARE_CLASS( APowerMorph, APowerup ) public: - void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); void SetNoCallUndoMorph() { bNoCallUndoMorph = true; } FNameNoInit PlayerClass, MorphFlash, UnMorphFlash; diff --git a/src/g_shared/a_camera.cpp b/src/g_shared/a_camera.cpp index 64868231bc..fb3d81533c 100644 --- a/src/g_shared/a_camera.cpp +++ b/src/g_shared/a_camera.cpp @@ -36,7 +36,7 @@ #include "info.h" #include "a_sharedglobal.h" #include "p_local.h" -#include "farchive.h" +#include "serializer.h" #include "math/cmath.h" /* @@ -55,7 +55,8 @@ public: void PostBeginPlay (); void Tick (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); protected: DAngle Center; DAngle Acc; @@ -65,10 +66,13 @@ protected: IMPLEMENT_CLASS (ASecurityCamera) -void ASecurityCamera::Serialize (FArchive &arc) +void ASecurityCamera::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Center << Acc << Delta << Range; + arc("center", Center) + ("acc", Acc) + ("delta", Delta) + ("range", Range); } void ASecurityCamera::PostBeginPlay () @@ -114,17 +118,18 @@ public: void PostBeginPlay (); void Tick (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); protected: DAngle MaxPitchChange; }; IMPLEMENT_CLASS (AAimingCamera) -void AAimingCamera::Serialize (FArchive &arc) +void AAimingCamera::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << MaxPitchChange; + arc("maxpitchchange", MaxPitchChange); } void AAimingCamera::PostBeginPlay () diff --git a/src/g_shared/a_decals.cpp b/src/g_shared/a_decals.cpp index 94e92e7258..e02ab1a52d 100644 --- a/src/g_shared/a_decals.cpp +++ b/src/g_shared/a_decals.cpp @@ -44,8 +44,9 @@ #include "d_net.h" #include "colormatcher.h" #include "v_palette.h" -#include "farchive.h" +#include "serializer.h" #include "doomdata.h" +#include "r_state.h" static double DecalWidth, DecalLeft, DecalRight; static double SpreadZ; @@ -58,7 +59,8 @@ static int ImpactCount; CVAR (Bool, cl_spreaddecals, true, CVAR_ARCHIVE) IMPLEMENT_POINTY_CLASS (DBaseDecal) - DECLARE_POINTER(WallNext) + DECLARE_POINTER(WallPrev) + DECLARE_POINTER(WallNext) END_POINTERS IMPLEMENT_CLASS (DImpactDecal) @@ -75,7 +77,7 @@ DBaseDecal::DBaseDecal () DBaseDecal::DBaseDecal (double z) : DThinker(STAT_DECAL), WallNext(0), WallPrev(0), LeftDistance(0), Z(z), ScaleX(1.), ScaleY(1.), Alpha(1.), - AlphaColor(0), Translation(0), RenderFlags(0) + AlphaColor(0), Translation(0), RenderFlags(0), Side(nullptr), Sector(nullptr) { RenderStyle = STYLE_None; PicNum.SetInvalid(); @@ -83,8 +85,8 @@ DBaseDecal::DBaseDecal (double z) DBaseDecal::DBaseDecal (int statnum, double z) : DThinker(statnum), - WallNext(0), WallPrev(0), LeftDistance(0), Z(z), ScaleX(1.), ScaleY(1.), Alpha(1.), - AlphaColor(0), Translation(0), RenderFlags(0) + WallNext(nullptr), WallPrev(nullptr), LeftDistance(0), Z(z), ScaleX(1.), ScaleY(1.), Alpha(1.), + AlphaColor(0), Translation(0), RenderFlags(0), Side(nullptr), Sector(nullptr) { RenderStyle = STYLE_None; PicNum.SetInvalid(); @@ -92,17 +94,17 @@ DBaseDecal::DBaseDecal (int statnum, double z) DBaseDecal::DBaseDecal (const AActor *basis) : DThinker(STAT_DECAL), - WallNext(0), WallPrev(0), LeftDistance(0), Z(basis->Z()), ScaleX(basis->Scale.X), ScaleY(basis->Scale.Y), + WallNext(nullptr), WallPrev(nullptr), LeftDistance(0), Z(basis->Z()), ScaleX(basis->Scale.X), ScaleY(basis->Scale.Y), Alpha(basis->Alpha), AlphaColor(basis->fillcolor), Translation(basis->Translation), PicNum(basis->picnum), - RenderFlags(basis->renderflags), RenderStyle(basis->RenderStyle) + RenderFlags(basis->renderflags), RenderStyle(basis->RenderStyle), Side(nullptr), Sector(nullptr) { } DBaseDecal::DBaseDecal (const DBaseDecal *basis) : DThinker(STAT_DECAL), - WallNext(0), WallPrev(0), LeftDistance(basis->LeftDistance), Z(basis->Z), ScaleX(basis->ScaleX), + WallNext(nullptr), WallPrev(nullptr), LeftDistance(basis->LeftDistance), Z(basis->Z), ScaleX(basis->ScaleX), ScaleY(basis->ScaleY), Alpha(basis->Alpha), AlphaColor(basis->AlphaColor), Translation(basis->Translation), - PicNum(basis->PicNum), RenderFlags(basis->RenderFlags), RenderStyle(basis->RenderStyle) + PicNum(basis->PicNum), RenderFlags(basis->RenderFlags), RenderStyle(basis->RenderStyle), Side(nullptr), Sector(nullptr) { } @@ -114,64 +116,35 @@ void DBaseDecal::Destroy () void DBaseDecal::Remove () { - DBaseDecal **prev = WallPrev; - DBaseDecal *next = WallNext; - if (prev && (*prev = next)) - next->WallPrev = prev; - WallPrev = NULL; - WallNext = NULL; + if (WallPrev == nullptr) + { + if (Side != nullptr) Side->AttachedDecals = WallNext; + } + else WallPrev->WallNext = WallNext; + + if (WallNext != nullptr) WallNext->WallPrev = WallPrev; + + WallPrev = nullptr; + WallNext = nullptr; } -void DBaseDecal::Serialize (FArchive &arc) +void DBaseDecal::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << LeftDistance - << Z - << ScaleX << ScaleY - << Alpha - << AlphaColor - << Translation - << PicNum - << RenderFlags - << RenderStyle - << Sector; -} - -void DBaseDecal::SerializeChain (FArchive &arc, DBaseDecal **first) -{ - DWORD numInChain; - DBaseDecal *fresh; - DBaseDecal **firstptr = first; - - if (arc.IsLoading ()) - { - numInChain = arc.ReadCount (); - - while (numInChain--) - { - arc << fresh; - *firstptr = fresh; - fresh->WallPrev = firstptr; - firstptr = &fresh->WallNext; - } - } - else - { - numInChain = 0; - fresh = *firstptr; - while (fresh != NULL) - { - fresh = fresh->WallNext; - ++numInChain; - } - arc.WriteCount (numInChain); - fresh = *firstptr; - while (numInChain--) - { - arc << fresh; - fresh = fresh->WallNext; - } - } + arc("wallprev", WallPrev) + ("wallnext", WallNext) + ("leftdistance", LeftDistance) + ("z", Z) + ("scalex", ScaleX) + ("scaley", ScaleY) + ("alpha", Alpha) + ("alphacolor", AlphaColor) + ("translation", Translation) + ("picnum", PicNum) + ("renderflags", RenderFlags) + ("renderstyle", RenderStyle) + ("side", Side) + ("sector", Sector); } void DBaseDecal::GetXY (side_t *wall, double &ox, double &oy) const @@ -211,26 +184,18 @@ void DBaseDecal::SetShade (int r, int g, int b) // Returns the texture the decal stuck to. FTextureID DBaseDecal::StickToWall (side_t *wall, double x, double y, F3DFloor *ffloor) { - // Stick the decal at the end of the chain so it appears on top - DBaseDecal *next, **prev; + Side = wall; + WallPrev = wall->AttachedDecals; - prev = &wall->AttachedDecals; - while (*prev != NULL) + while (WallPrev != nullptr && WallPrev->WallNext != nullptr) { - next = *prev; - prev = &next->WallNext; + WallPrev = WallPrev->WallNext; } + if (WallPrev != nullptr) WallPrev->WallNext = this; + else wall->AttachedDecals = this; + WallNext = nullptr; + - *prev = this; - WallNext = NULL; - WallPrev = prev; -/* - WallNext = wall->AttachedDecals; - WallPrev = &wall->AttachedDecals; - if (WallNext) - WallNext->WallPrev = &WallNext; - wall->AttachedDecals = this; -*/ sector_t *front, *back; line_t *line; FTextureID tex; @@ -592,28 +557,6 @@ CUSTOM_CVAR (Int, cl_maxdecals, 1024, CVAR_ARCHIVE) } } -// Uses: target points to previous impact decal -// tracer points to next impact decal -// -// Note that this means we can't simply serialize an impact decal as-is -// because doing so when many are present in a level could result in -// a lot of recursion and we would run out of stack. Not nice. So instead, -// the save game code calls DImpactDecal::SerializeAll to serialize a -// list of impact decals. - -void DImpactDecal::SerializeTime (FArchive &arc) -{ - if (arc.IsLoading ()) - { - ImpactCount = 0; - } -} - -void DImpactDecal::Serialize (FArchive &arc) -{ - Super::Serialize (arc); -} - DImpactDecal::DImpactDecal () : DBaseDecal (STAT_AUTODECAL, 0.) { diff --git a/src/g_shared/a_flashfader.cpp b/src/g_shared/a_flashfader.cpp index b5b4a05496..71673e5c7d 100644 --- a/src/g_shared/a_flashfader.cpp +++ b/src/g_shared/a_flashfader.cpp @@ -1,7 +1,7 @@ #include "a_sharedglobal.h" #include "g_level.h" #include "d_player.h" -#include "farchive.h" +#include "serializer.h" IMPLEMENT_POINTY_CLASS (DFlashFader) DECLARE_POINTER (ForWho) @@ -26,13 +26,13 @@ void DFlashFader::Destroy () Super::Destroy(); } -void DFlashFader::Serialize (FArchive &arc) +void DFlashFader::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TotalTics << StartTic << ForWho; - for (int i = 1; i >= 0; --i) - for (int j = 3; j >= 0; --j) - arc << Blends[i][j]; + arc("totaltics", TotalTics) + ("starttic", StartTic) + ("forwho", ForWho) + .Array("blends", Blends[0], 8); } void DFlashFader::Tick () diff --git a/src/g_shared/a_lightning.cpp b/src/g_shared/a_lightning.cpp index 2f2c4e2354..3eaedffc9c 100644 --- a/src/g_shared/a_lightning.cpp +++ b/src/g_shared/a_lightning.cpp @@ -9,7 +9,7 @@ #include "r_sky.h" #include "g_level.h" #include "r_state.h" -#include "farchive.h" +#include "serializer.h" static FRandom pr_lightning ("Lightning"); @@ -19,44 +19,24 @@ DLightningThinker::DLightningThinker () : DThinker (STAT_LIGHTNING) { Stopped = false; - LightningLightLevels = NULL; LightningFlashCount = 0; NextLightningFlash = ((pr_lightning()&15)+5)*35; // don't flash at level start - LightningLightLevels = new short[numsectors]; - clearbufshort(LightningLightLevels, numsectors, SHRT_MAX); + LightningLightLevels.Resize(numsectors); + clearbufshort(&LightningLightLevels[0], numsectors, SHRT_MAX); } DLightningThinker::~DLightningThinker () { - if (LightningLightLevels != NULL) - { - delete[] LightningLightLevels; - } } -void DLightningThinker::Serialize (FArchive &arc) +void DLightningThinker::Serialize(FSerializer &arc) { - int i; - short *lights; - Super::Serialize (arc); - - arc << Stopped << NextLightningFlash << LightningFlashCount; - - if (arc.IsLoading ()) - { - if (LightningLightLevels != NULL) - { - delete[] LightningLightLevels; - } - LightningLightLevels = new short[numsectors]; - } - lights = LightningLightLevels; - for (i = numsectors; i > 0; ++lights, --i) - { - arc << *lights; - } + arc("stopped", Stopped) + ("next", NextLightningFlash) + ("count", LightningFlashCount) + ("levels", LightningLightLevels); } void DLightningThinker::Tick () @@ -107,7 +87,7 @@ void DLightningThinker::LightningFlash () tempSec->SetLightLevel(LightningLightLevels[j]); } } - clearbufshort(LightningLightLevels, numsectors, SHRT_MAX); + clearbufshort(&LightningLightLevels[0], numsectors, SHRT_MAX); level.flags &= ~LEVEL_SWAPSKIES; } return; diff --git a/src/g_shared/a_lightning.h b/src/g_shared/a_lightning.h index 1c4ca2a650..62a2604740 100644 --- a/src/g_shared/a_lightning.h +++ b/src/g_shared/a_lightning.h @@ -13,7 +13,7 @@ class DLightningThinker : public DThinker public: DLightningThinker (); ~DLightningThinker (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); void ForceLightning (int mode); void TerminateLightning(); @@ -24,7 +24,7 @@ protected: int NextLightningFlash; int LightningFlashCount; bool Stopped; - short *LightningLightLevels; + TArray LightningLightLevels; }; void P_StartLightning (); diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index 57271f768d..46ae5cac7b 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -11,9 +11,10 @@ #include "a_morph.h" #include "doomstat.h" #include "g_level.h" -#include "farchive.h" +#include "serializer.h" #include "p_enemy.h" #include "d_player.h" +#include "r_data/sprites.h" static FRandom pr_morphmonst ("MorphMonster"); @@ -634,10 +635,16 @@ int AMorphProjectile::DoSpecialDamage (AActor *target, int damage, FName damaget return -1; } -void AMorphProjectile::Serialize (FArchive &arc) +void AMorphProjectile::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << PlayerClass << MonsterClass << Duration << MorphStyle << MorphFlash << UnMorphFlash; + auto def = (AMorphProjectile*)GetDefault(); + arc("playerclass", PlayerClass, def->PlayerClass) + ("monsterclass", MonsterClass, def->MonsterClass) + ("duration", Duration, def->Duration) + ("morphstyle", MorphStyle, def->MorphStyle) + ("morphflash", MorphFlash, def->MorphFlash) + ("unmorphflash", UnMorphFlash, def->UnMorphFlash); } @@ -647,10 +654,14 @@ IMPLEMENT_POINTY_CLASS (AMorphedMonster) DECLARE_POINTER (UnmorphedMe) END_POINTERS -void AMorphedMonster::Serialize (FArchive &arc) +void AMorphedMonster::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << UnmorphedMe << UnmorphTime << MorphStyle << MorphExitFlash << FlagsSave; + arc("unmorphedme", UnmorphedMe) + ("unmorphtime", UnmorphTime) + ("morphstyle", MorphStyle) + ("morphexitflash", MorphExitFlash) + ("flagsave", FlagsSave); } void AMorphedMonster::Destroy () diff --git a/src/g_shared/a_movingcamera.cpp b/src/g_shared/a_movingcamera.cpp index 202e1823f1..76f128b15a 100644 --- a/src/g_shared/a_movingcamera.cpp +++ b/src/g_shared/a_movingcamera.cpp @@ -37,7 +37,7 @@ #include "p_local.h" #include "p_lnspec.h" #include "doomstat.h" -#include "farchive.h" +#include "serializer.h" /* == InterpolationPoint: node along a camera's path @@ -60,7 +60,8 @@ public: AInterpolationPoint *ScanForLoop (); void FormChain (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); TObjPtr Next; }; @@ -69,10 +70,10 @@ IMPLEMENT_POINTY_CLASS (AInterpolationPoint) DECLARE_POINTER (Next) END_POINTERS -void AInterpolationPoint::Serialize (FArchive &arc) +void AInterpolationPoint::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Next; + arc("next", Next); } void AInterpolationPoint::BeginPlay () @@ -166,7 +167,8 @@ protected: virtual bool Interpolate (); virtual void NewNode (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); bool bActive, bJustStepped; TObjPtr PrevNode, CurrNode; @@ -179,10 +181,15 @@ IMPLEMENT_POINTY_CLASS (APathFollower) DECLARE_POINTER (CurrNode) END_POINTERS -void APathFollower::Serialize (FArchive &arc) +void APathFollower::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << bActive << bJustStepped << PrevNode << CurrNode << Time << HoldTime; + arc("active", bActive) + ("juststepped", bJustStepped) + ("prevnode", PrevNode) + ("currnode", CurrNode) + ("time", Time) + ("holdtime", HoldTime); } // Interpolate between p2 and p3 along a Catmull-Rom spline @@ -577,7 +584,8 @@ class AMovingCamera : public APathFollower public: void PostBeginPlay (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); protected: bool Interpolate (); @@ -588,10 +596,10 @@ IMPLEMENT_POINTY_CLASS (AMovingCamera) DECLARE_POINTER (Activator) END_POINTERS -void AMovingCamera::Serialize (FArchive &arc) +void AMovingCamera::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Activator; + arc("activator", Activator); } void AMovingCamera::PostBeginPlay () diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index c723662016..e80e548183 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -18,9 +18,9 @@ #include "g_level.h" #include "g_game.h" #include "doomstat.h" -#include "farchive.h" #include "d_player.h" #include "p_spec.h" +#include "serializer.h" static FRandom pr_restore ("RestorePos"); @@ -89,10 +89,12 @@ IMPLEMENT_CLASS (AAmmo) // //=========================================================================== -void AAmmo::Serialize (FArchive &arc) +void AAmmo::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << BackpackAmount << BackpackMaxAmount; + auto def = (AAmmo*)GetDefault(); + arc("backpackamount", BackpackAmount, def->BackpackAmount) + ("backpackmaxamount", BackpackMaxAmount, def->BackpackMaxAmount); } //=========================================================================== @@ -514,10 +516,21 @@ void AInventory::Tick () // //=========================================================================== -void AInventory::Serialize (FArchive &arc) +void AInventory::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Owner << Amount << MaxAmount << RespawnTics << ItemFlags << Icon << PickupSound << SpawnPointClass; + + auto def = (AInventory*)GetDefault(); + arc("owner", Owner) + ("amount", Amount, def->Amount) + ("maxamount", MaxAmount, def->MaxAmount) + ("interhubamount", InterHubAmount, def->InterHubAmount) + ("respawntics", RespawnTics, def->RespawnTics) + ("itemflags", ItemFlags, def->ItemFlags) + ("icon", Icon, def->Icon) + ("pickupsound", PickupSound, def->PickupSound) + ("spawnpointclass", SpawnPointClass, def->SpawnPointClass) + ("droptime", DropTime, def->DropTime); } //=========================================================================== @@ -1809,10 +1822,11 @@ bool AHealthPickup::Use (bool pickup) // //=========================================================================== -void AHealthPickup::Serialize (FArchive &arc) +void AHealthPickup::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << autousemode; + auto def = (AHealthPickup*)GetDefault(); + arc("autousemode", autousemode, def->autousemode); } // Backpack ----------------------------------------------------------------- @@ -1823,10 +1837,11 @@ void AHealthPickup::Serialize (FArchive &arc) // //=========================================================================== -void ABackpackItem::Serialize (FArchive &arc) +void ABackpackItem::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << bDepleted; + auto def = (ABackpackItem*)GetDefault(); + arc("bdepleted", bDepleted, def->bDepleted); } //=========================================================================== diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 0023cd271c..9dba52639a 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -153,7 +153,8 @@ class AInventory : public AActor HAS_OBJECT_POINTERS public: virtual void Touch (AActor *toucher); - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual void MarkPrecacheSounds() const; virtual void BeginPlay (); @@ -185,7 +186,7 @@ public: int RespawnTics; // Tics from pickup time to respawn time FTextureID Icon; // Icon to show on status bar or HUD int DropTime; // Countdown after dropping - const PClass *SpawnPointClass; // For respawning like Heretic's mace + PClassActor *SpawnPointClass; // For respawning like Heretic's mace DWORD ItemFlags; PClassActor *PickupFlash; // actor to spawn as pickup flash @@ -254,7 +255,8 @@ class AAmmo : public AInventory { DECLARE_CLASS_WITH_META(AAmmo, AInventory, PClassAmmo) public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); AInventory *CreateCopy (AActor *other); bool HandlePickup (AInventory *item); PClassActor *GetParentAmmo () const; @@ -311,7 +313,8 @@ public: bool bAltFire; // Set when this weapon's alternate fire is used. virtual void MarkPrecacheSounds() const; - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual bool ShouldStay (); virtual void AttachToOwner (AActor *other); virtual bool HandlePickup (AInventory *item); @@ -395,7 +398,8 @@ class AWeaponGiver : public AWeapon public: bool TryPickup(AActor *&toucher); - void Serialize(FArchive &arc); + + void Serialize(FSerializer &arc); double DropAmmoFactor; }; @@ -431,7 +435,8 @@ class AHealthPickup : public AInventory public: int autousemode; - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual AInventory *CreateCopy (AActor *other); virtual AInventory *CreateTossable (); virtual bool HandlePickup (AInventory *item); @@ -451,7 +456,8 @@ class ABasicArmor : public AArmor { DECLARE_CLASS (ABasicArmor, AArmor) public: - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual void Tick (); virtual AInventory *CreateCopy (AActor *other); virtual bool HandlePickup (AInventory *item); @@ -471,7 +477,8 @@ class ABasicArmorPickup : public AArmor { DECLARE_CLASS (ABasicArmorPickup, AArmor) public: - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual AInventory *CreateCopy (AActor *other); virtual bool Use (bool pickup); @@ -486,7 +493,8 @@ class ABasicArmorBonus : public AArmor { DECLARE_CLASS (ABasicArmorBonus, AArmor) public: - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual AInventory *CreateCopy (AActor *other); virtual bool Use (bool pickup); @@ -505,7 +513,8 @@ class AHexenArmor : public AArmor { DECLARE_CLASS (AHexenArmor, AArmor) public: - virtual void Serialize (FArchive &arc); + + virtual void Serialize(FSerializer &arc); virtual AInventory *CreateCopy (AActor *other); virtual AInventory *CreateTossable (); virtual bool HandlePickup (AInventory *item); @@ -533,7 +542,7 @@ class APuzzleItem : public AInventory { DECLARE_CLASS_WITH_META(APuzzleItem, AInventory, PClassPuzzleItem) public: - void Serialize (FArchive &arc); + bool ShouldStay (); bool Use (bool pickup); bool HandlePickup (AInventory *item); @@ -555,7 +564,8 @@ class ABackpackItem : public AInventory { DECLARE_CLASS (ABackpackItem, AInventory) public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); bool HandlePickup (AInventory *item); AInventory *CreateCopy (AActor *other); AInventory *CreateTossable (); diff --git a/src/g_shared/a_puzzleitems.cpp b/src/g_shared/a_puzzleitems.cpp index dff532f176..233066edc7 100644 --- a/src/g_shared/a_puzzleitems.cpp +++ b/src/g_shared/a_puzzleitems.cpp @@ -7,7 +7,6 @@ #include "c_console.h" #include "doomstat.h" #include "v_font.h" -#include "farchive.h" IMPLEMENT_CLASS(PClassPuzzleItem) @@ -20,12 +19,6 @@ void PClassPuzzleItem::DeriveData(PClass *newclass) IMPLEMENT_CLASS(APuzzleItem) -void APuzzleItem::Serialize (FArchive &arc) -{ - Super::Serialize (arc); - arc << PuzzleItemNumber; -} - bool APuzzleItem::HandlePickup (AInventory *item) { // Can't carry more than 1 of each puzzle item in coop netplay diff --git a/src/g_shared/a_quake.cpp b/src/g_shared/a_quake.cpp index 63394e02a3..98e505b2b9 100644 --- a/src/g_shared/a_quake.cpp +++ b/src/g_shared/a_quake.cpp @@ -8,7 +8,7 @@ #include "s_sound.h" #include "a_sharedglobal.h" #include "statnums.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" #include "r_utility.h" @@ -64,15 +64,23 @@ DEarthquake::DEarthquake(AActor *center, int intensityX, int intensityY, int int // //========================================================================== -void DEarthquake::Serialize (FArchive &arc) +void DEarthquake::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Spot << m_Intensity << m_Countdown - << m_TremorRadius << m_DamageRadius - << m_QuakeSFX << m_Flags << m_CountdownStart - << m_WaveSpeed - << m_Falloff << m_Highpoint << m_MiniCount - << m_RollIntensity << m_RollWave; + arc("spot", m_Spot) + ("intensity", m_Intensity) + ("countdown", m_Countdown) + ("tremorradius", m_TremorRadius) + ("damageradius", m_DamageRadius) + ("quakesfx", m_QuakeSFX) + ("quakeflags", m_Flags) + ("countdownstart", m_CountdownStart) + ("wavespeed", m_WaveSpeed) + ("falloff", m_Falloff) + ("highpoint", m_Highpoint) + ("minicount", m_MiniCount) + ("rollintensity", m_RollIntensity) + ("rollwave", m_RollWave); } //========================================================================== diff --git a/src/g_shared/a_sectoraction.cpp b/src/g_shared/a_sectoraction.cpp index 397fa1a89b..38435e16be 100644 --- a/src/g_shared/a_sectoraction.cpp +++ b/src/g_shared/a_sectoraction.cpp @@ -49,23 +49,27 @@ bool ASectorAction::IsActivatedByUse() const void ASectorAction::Destroy () { - // Remove ourself from this sector's list of actions - AActor *probe = Sector->SecActTarget; - union + if (Sector != nullptr) { - AActor **act; - ASectorAction **secact; - } prev; - prev.secact = &Sector->SecActTarget; + // Remove ourself from this sector's list of actions + AActor *probe = Sector->SecActTarget; + union + { + AActor **act; + ASectorAction **secact; + } prev; + prev.secact = &Sector->SecActTarget; - while (probe && probe != this) - { - prev.act = &probe->tracer; - probe = probe->tracer; - } - if (probe != NULL) - { - *prev.act = probe->tracer; + while (probe && probe != this) + { + prev.act = &probe->tracer; + probe = probe->tracer; + } + if (probe != nullptr) + { + *prev.act = probe->tracer; + } + Sector = nullptr; } Super::Destroy (); diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index 029653bb15..c00cd8cf0c 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -24,7 +24,7 @@ public: DBaseDecal (const AActor *actor); DBaseDecal (const DBaseDecal *basis); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Destroy (); FTextureID StickToWall(side_t *wall, double x, double y, F3DFloor * ffloor); double GetRealZ (const side_t *wall) const; @@ -33,9 +33,7 @@ public: void Spread (const FDecalTemplate *tpl, side_t *wall, double x, double y, double z, F3DFloor * ffloor); void GetXY (side_t *side, double &x, double &y) const; - static void SerializeChain (FArchive &arc, DBaseDecal **firstptr); - - DBaseDecal *WallNext, **WallPrev; + DBaseDecal *WallNext, *WallPrev; double LeftDistance; double Z; @@ -46,7 +44,8 @@ public: FTextureID PicNum; DWORD RenderFlags; FRenderStyle RenderStyle; - sector_t * Sector; // required for 3D floors + side_t *Side; + sector_t *Sector; protected: virtual DBaseDecal *CloneSelf(const FDecalTemplate *tpl, double x, double y, double z, side_t *wall, F3DFloor * ffloor) const; @@ -70,9 +69,6 @@ public: void BeginPlay (); void Destroy (); - void Serialize (FArchive &arc); - static void SerializeTime (FArchive &arc); - protected: DBaseDecal *CloneSelf(const FDecalTemplate *tpl, double x, double y, double z, side_t *wall, F3DFloor * ffloor) const; static void CheckMax (); @@ -122,7 +118,7 @@ public: float r2, float g2, float b2, float a2, float time, AActor *who); void Destroy (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); AActor *WhoFor() { return ForWho; } void Cancel (); @@ -165,7 +161,7 @@ public: int damrad, int tremrad, FSoundID quakesfx, int flags, double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint, double rollIntensity, double rollWave); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); TObjPtr m_Spot; double m_TremorRadius, m_DamageRadius; @@ -194,7 +190,8 @@ class AMorphProjectile : public AActor DECLARE_CLASS (AMorphProjectile, AActor) public: int DoSpecialDamage (AActor *target, int damage, FName damagetype); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); FNameNoInit PlayerClass, MonsterClass, MorphFlash, UnMorphFlash; int Duration, MorphStyle; @@ -206,7 +203,8 @@ class AMorphedMonster : public AActor HAS_OBJECT_POINTERS public: void Tick (); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void Die (AActor *source, AActor *inflictor, int dmgflags); void Destroy (); diff --git a/src/g_shared/a_skies.cpp b/src/g_shared/a_skies.cpp index 1da7cd7120..89c785e3ef 100644 --- a/src/g_shared/a_skies.cpp +++ b/src/g_shared/a_skies.cpp @@ -36,8 +36,8 @@ #include "a_sharedglobal.h" #include "p_local.h" #include "p_lnspec.h" -#include "farchive.h" #include "r_sky.h" +#include "r_state.h" #include "portal.h" // arg0 = Visibility*4 for this skybox @@ -170,7 +170,10 @@ void ASectorSilencer::BeginPlay () void ASectorSilencer::Destroy () { - Sector->Flags &= ~SECF_SILENT; + if (Sector != nullptr) + { + Sector->Flags &= ~SECF_SILENT; + } Super::Destroy (); } diff --git a/src/g_shared/a_soundenvironment.cpp b/src/g_shared/a_soundenvironment.cpp index 645298da86..ba01711573 100644 --- a/src/g_shared/a_soundenvironment.cpp +++ b/src/g_shared/a_soundenvironment.cpp @@ -59,7 +59,7 @@ void ASoundEnvironment::PostBeginPlay () void ASoundEnvironment::Activate (AActor *activator) { - zones[Sector->ZoneNumber].Environment = S_FindEnvironment ((args[0]<<8) | (args[1])); + Zones[Sector->ZoneNumber].Environment = S_FindEnvironment ((args[0]<<8) | (args[1])); } // Deactivate just exists so that you can flag the thing as dormant in an editor diff --git a/src/g_shared/a_soundsequence.cpp b/src/g_shared/a_soundsequence.cpp index b7ef2d25b2..f09b5b7adc 100644 --- a/src/g_shared/a_soundsequence.cpp +++ b/src/g_shared/a_soundsequence.cpp @@ -65,7 +65,7 @@ #include "s_sound.h" #include "m_random.h" #include "s_sndseq.h" -#include "farchive.h" +#include "serializer.h" // SoundSequenceSlot -------------------------------------------------------- @@ -74,7 +74,8 @@ class ASoundSequenceSlot : public AActor DECLARE_CLASS (ASoundSequenceSlot, AActor) HAS_OBJECT_POINTERS public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); TObjPtr Sequence; }; @@ -89,10 +90,10 @@ END_POINTERS // //========================================================================== -void ASoundSequenceSlot::Serialize (FArchive &arc) +void ASoundSequenceSlot::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << Sequence; + arc("sequence", Sequence); } // SoundSequence ------------------------------------------------------------ diff --git a/src/g_shared/a_specialspot.cpp b/src/g_shared/a_specialspot.cpp index a0469fb40f..1d8d1f72d5 100644 --- a/src/g_shared/a_specialspot.cpp +++ b/src/g_shared/a_specialspot.cpp @@ -39,7 +39,7 @@ #include "i_system.h" #include "thingdef/thingdef.h" #include "doomstat.h" -#include "farchive.h" +#include "serializer.h" #include "a_pickups.h" static FRandom pr_spot ("SpecialSpot"); @@ -58,7 +58,7 @@ TObjPtr DSpotState::SpotState; struct FSpotList { - const PClass *Type; + PClassActor *Type; TArray Spots; unsigned Index; int SkipCount; @@ -68,7 +68,7 @@ struct FSpotList { } - FSpotList(const PClass *type) + FSpotList(PClassActor *type) { Type = type; Index = 0; @@ -82,17 +82,6 @@ struct FSpotList // //---------------------------------------------------------------------------- - void Serialize(FArchive &arc) - { - arc << Type << Spots << Index << SkipCount << numcalls; - } - - //---------------------------------------------------------------------------- - // - // - // - //---------------------------------------------------------------------------- - bool Add(ASpecialSpot *newspot) { for(unsigned i = 0; i < Spots.Size(); i++) @@ -194,6 +183,26 @@ struct FSpotList // //---------------------------------------------------------------------------- +FSerializer &Serialize(FSerializer &arc, const char *key, FSpotList &list, FSpotList *def) +{ + if (arc.BeginObject(key)) + { + arc("type", list.Type) + ("spots", list.Spots) + ("index", list.Index) + ("skipcount", list.SkipCount) + ("numcalls", list.numcalls) + .EndObject(); + } + return arc; +} + +//---------------------------------------------------------------------------- +// +// +// +//---------------------------------------------------------------------------- + DSpotState::DSpotState () : DThinker (STAT_INFO) { @@ -215,10 +224,6 @@ DSpotState::DSpotState () void DSpotState::Destroy () { - for(unsigned i = 0; i < SpotLists.Size(); i++) - { - delete SpotLists[i]; - } SpotLists.Clear(); SpotLists.ShrinkToFit(); @@ -254,13 +259,13 @@ DSpotState *DSpotState::GetSpotState(bool create) // //---------------------------------------------------------------------------- -FSpotList *DSpotState::FindSpotList(const PClass *type) +FSpotList *DSpotState::FindSpotList(PClassActor *type) { for(unsigned i = 0; i < SpotLists.Size(); i++) { - if (SpotLists[i]->Type == type) return SpotLists[i]; + if (SpotLists[i].Type == type) return &SpotLists[i]; } - return SpotLists[SpotLists.Push(new FSpotList(type))]; + return &SpotLists[SpotLists.Push(FSpotList(type))]; } //---------------------------------------------------------------------------- @@ -295,27 +300,10 @@ bool DSpotState::RemoveSpot(ASpecialSpot *spot) // //---------------------------------------------------------------------------- -void DSpotState::Serialize(FArchive &arc) +void DSpotState::Serialize(FSerializer &arc) { Super::Serialize(arc); - if (arc.IsStoring()) - { - arc.WriteCount(SpotLists.Size()); - for(unsigned i = 0; i < SpotLists.Size(); i++) - { - SpotLists[i]->Serialize(arc); - } - } - else - { - unsigned c = arc.ReadCount(); - SpotLists.Resize(c); - for(unsigned i = 0; i < SpotLists.Size(); i++) - { - SpotLists[i] = new FSpotList; - SpotLists[i]->Serialize(arc); - } - } + arc("spots", SpotLists); } //---------------------------------------------------------------------------- @@ -324,7 +312,7 @@ void DSpotState::Serialize(FArchive &arc) // //---------------------------------------------------------------------------- -ASpecialSpot *DSpotState::GetNextInList(const PClass *type, int skipcounter) +ASpecialSpot *DSpotState::GetNextInList(PClassActor *type, int skipcounter) { FSpotList *list = FindSpotList(type); if (list != NULL) return list->GetNextInList(skipcounter); @@ -337,7 +325,7 @@ ASpecialSpot *DSpotState::GetNextInList(const PClass *type, int skipcounter) // //---------------------------------------------------------------------------- -ASpecialSpot *DSpotState::GetSpotWithMinMaxDistance(const PClass *type, double x, double y, double mindist, double maxdist) +ASpecialSpot *DSpotState::GetSpotWithMinMaxDistance(PClassActor *type, double x, double y, double mindist, double maxdist) { FSpotList *list = FindSpotList(type); if (list != NULL) return list->GetSpotWithMinMaxDistance(x, y, mindist, maxdist); @@ -350,7 +338,7 @@ ASpecialSpot *DSpotState::GetSpotWithMinMaxDistance(const PClass *type, double x // //---------------------------------------------------------------------------- -ASpecialSpot *DSpotState::GetRandomSpot(const PClass *type, bool onlyonce) +ASpecialSpot *DSpotState::GetRandomSpot(PClassActor *type, bool onlyonce) { FSpotList *list = FindSpotList(type); if (list != NULL) return list->GetRandomSpot(onlyonce); diff --git a/src/g_shared/a_specialspot.h b/src/g_shared/a_specialspot.h index db148b19dc..8fe38608aa 100644 --- a/src/g_shared/a_specialspot.h +++ b/src/g_shared/a_specialspot.h @@ -22,7 +22,7 @@ class DSpotState : public DThinker { DECLARE_CLASS(DSpotState, DThinker) static TObjPtr SpotState; - TArray SpotLists; + TArray SpotLists; public: @@ -31,13 +31,13 @@ public: void Destroy (); void Tick (); static DSpotState *GetSpotState(bool create = true); - FSpotList *FindSpotList(const PClass *type); + FSpotList *FindSpotList(PClassActor *type); bool AddSpot(ASpecialSpot *spot); bool RemoveSpot(ASpecialSpot *spot); - void Serialize(FArchive &arc); - ASpecialSpot *GetNextInList(const PClass *type, int skipcounter); - ASpecialSpot *GetSpotWithMinMaxDistance(const PClass *type, double x, double y, double mindist, double maxdist); - ASpecialSpot *GetRandomSpot(const PClass *type, bool onlyonce = false); + void Serialize(FSerializer &arc); + ASpecialSpot *GetNextInList(PClassActor *type, int skipcounter); + ASpecialSpot *GetSpotWithMinMaxDistance(PClassActor *type, double x, double y, double mindist, double maxdist); + ASpecialSpot *GetRandomSpot(PClassActor *type, bool onlyonce = false); }; diff --git a/src/g_shared/a_weaponpiece.cpp b/src/g_shared/a_weaponpiece.cpp index 2d7549db7f..aefce18c1c 100644 --- a/src/g_shared/a_weaponpiece.cpp +++ b/src/g_shared/a_weaponpiece.cpp @@ -1,7 +1,7 @@ #include "a_pickups.h" #include "a_weaponpiece.h" #include "doomstat.h" -#include "farchive.h" +#include "serializer.h" IMPLEMENT_CLASS(PClassWeaponPiece) IMPLEMENT_CLASS (AWeaponHolder) @@ -17,10 +17,11 @@ void PClassWeaponPiece::ReplaceClassRef(PClass *oldclass, PClass *newclass) } -void AWeaponHolder::Serialize (FArchive &arc) +void AWeaponHolder::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << PieceMask << PieceWeapon; + arc("piecemask", PieceMask) + ("pieceweapon", PieceWeapon); } @@ -29,10 +30,13 @@ IMPLEMENT_POINTY_CLASS (AWeaponPiece) END_POINTERS -void AWeaponPiece::Serialize (FArchive &arc) +void AWeaponPiece::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << WeaponClass << FullWeapon << PieceValue; + auto def = (AWeaponPiece*)GetDefault(); + arc("weaponclass", WeaponClass, def->WeaponClass) + ("fullweapon", FullWeapon) + ("piecevalue", PieceValue, def->PieceValue); } //========================================================================== diff --git a/src/g_shared/a_weaponpiece.h b/src/g_shared/a_weaponpiece.h index 071ea68911..88f3da02c7 100644 --- a/src/g_shared/a_weaponpiece.h +++ b/src/g_shared/a_weaponpiece.h @@ -15,7 +15,8 @@ class AWeaponPiece : public AInventory protected: bool PrivateShouldStay (); public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); bool TryPickup (AActor *&toucher); bool TryPickupRestricted (AActor *&toucher); bool ShouldStay (); @@ -23,7 +24,7 @@ public: virtual void PlayPickupSound (AActor *toucher); int PieceValue; - PClassWeapon *WeaponClass; + PClassActor *WeaponClass; TObjPtr FullWeapon; }; @@ -35,7 +36,8 @@ class AWeaponHolder : public AInventory public: int PieceMask; - const PClass * PieceWeapon; + PClassActor * PieceWeapon; - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); }; diff --git a/src/g_shared/a_weapons.cpp b/src/g_shared/a_weapons.cpp index 6143f52066..edd250debd 100644 --- a/src/g_shared/a_weapons.cpp +++ b/src/g_shared/a_weapons.cpp @@ -17,7 +17,7 @@ #include "doomstat.h" #include "g_level.h" #include "d_net.h" -#include "farchive.h" +#include "serializer.h" #define BONUSADD 6 @@ -73,28 +73,45 @@ void PClassWeapon::ReplaceClassRef(PClass *oldclass, PClass *newclass) // //=========================================================================== -void AWeapon::Serialize (FArchive &arc) +void AWeapon::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << WeaponFlags - << AmmoType1 << AmmoType2 - << AmmoGive1 << AmmoGive2 - << MinAmmo1 << MinAmmo2 - << AmmoUse1 << AmmoUse2 - << Kickback - << YAdjust - << UpSound << ReadySound - << SisterWeaponType - << ProjectileType << AltProjectileType - << SelectionOrder - << MoveCombatDist - << Ammo1 << Ammo2 << SisterWeapon << GivenAsMorphWeapon - << bAltFire - << ReloadCounter - << BobStyle << BobSpeed << BobRangeX << BobRangeY - << FOVScale - << Crosshair - << MinSelAmmo1 << MinSelAmmo2; + auto def = (AWeapon*)GetDefault(); + arc("weaponflags", WeaponFlags, def->WeaponFlags) + ("ammogive1", AmmoGive1, def->AmmoGive1) + ("ammogive2", AmmoGive2, def->AmmoGive2) + ("minammo1", MinAmmo1, def->MinAmmo1) + ("minammo2", MinAmmo2, def->MinAmmo2) + ("ammouse1", AmmoUse1, def->AmmoUse1) + ("ammouse2", AmmoUse2, def->AmmoUse2) + ("kickback", Kickback, Kickback) + ("yadjust", YAdjust, def->YAdjust) + ("upsound", UpSound, def->UpSound) + ("readysound", ReadySound, def->ReadySound) + ("selectionorder", SelectionOrder, def->SelectionOrder) + ("ammo1", Ammo1) + ("ammo2", Ammo2) + ("sisterweapon", SisterWeapon) + ("givenasmorphweapon", GivenAsMorphWeapon, def->GivenAsMorphWeapon) + ("altfire", bAltFire, def->bAltFire) + ("reloadcounter", ReloadCounter, def->ReloadCounter) + ("bobstyle", BobStyle, def->BobStyle) + ("bobspeed", BobSpeed, def->BobSpeed) + ("bobrangex", BobRangeX, def->BobRangeX) + ("bobrangey", BobRangeY, def->BobRangeY) + ("fovscale", FOVScale, def->FOVScale) + ("crosshair", Crosshair, def->Crosshair) + ("minselammo1", MinSelAmmo1, def->MinSelAmmo1) + ("minselammo2", MinSelAmmo2, def->MinSelAmmo2); + /* these can never change + ("ammotype1", AmmoType1, def->AmmoType1) + ("ammotype2", AmmoType2, def->AmmoType2) + ("sisterweapontype", SisterWeaponType, def->SisterWeaponType) + ("projectiletype", ProjectileType, def->ProjectileType) + ("altprojectiletype", AltProjectileType, def->AltProjectileType) + ("movecombatdist", MoveCombatDist, def->MoveCombatDist) + */ + } //=========================================================================== @@ -741,10 +758,11 @@ FState *AWeapon::GetStateForButtonName (FName button) IMPLEMENT_CLASS(AWeaponGiver) -void AWeaponGiver::Serialize(FArchive &arc) +void AWeaponGiver::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << DropAmmoFactor; + auto def = (AWeaponGiver *)GetDefault(); + arc("dropammofactor", DropAmmoFactor, def->DropAmmoFactor); } bool AWeaponGiver::TryPickup(AActor *&toucher) diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp index 32a8d2de52..b86cf64175 100644 --- a/src/g_shared/hudmessages.cpp +++ b/src/g_shared/hudmessages.cpp @@ -39,7 +39,7 @@ #include "v_video.h" #include "cmdlib.h" #include "doomstat.h" -#include "farchive.h" +#include "serializer.h" EXTERN_CVAR(Int, con_scaletext) int active_con_scaletext(); @@ -56,14 +56,6 @@ IMPLEMENT_CLASS (DHUDMessageTypeOnFadeOut) * Basic HUD message. Appears and disappears without any special effects * *************************************************************************/ -inline FArchive &operator<< (FArchive &arc, EColorRange &i) -{ - BYTE val = (BYTE)i; - arc << val; - i = (EColorRange)val; - return arc; -} - //============================================================================ // // DHUDMessage Constructor @@ -180,20 +172,34 @@ DHUDMessage::~DHUDMessage () // //============================================================================ -void DHUDMessage::Serialize(FArchive &arc) +void DHUDMessage::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << Left << Top << CenterX << HoldTics - << Tics << State << TextColor - << SBarID << SourceText << Font << Next - << HUDWidth << HUDHeight - << NoWrap - << ClipX << ClipY << ClipWidth << ClipHeight - << WrapWidth - << HandleAspect - << VisibilityFlags - << Style << Alpha; - if (arc.IsLoading()) + arc("left", Left) + ("top", Top) + ("centerx", CenterX) + ("holdtics", HoldTics) + ("tics", Tics) + ("state", State) + .Enum("textcolor", TextColor) + ("sbarid", SBarID) + ("sourcetext", SourceText) + ("font", Font) + ("next", Next) + ("hudwidth", HUDWidth) + ("hudheight", HUDHeight) + ("nowrap", NoWrap) + ("clipx", ClipX) + ("clipy", ClipY) + ("clipwidth", ClipWidth) + ("clipheight", ClipHeight) + ("wrapwidth", WrapWidth) + ("handleaspect", HandleAspect) + ("visibilityflags", VisibilityFlags) + ("style", Style) + ("alpha", Alpha); + + if (arc.isReading()) { Lines = NULL; ResetText(SourceText); @@ -504,10 +510,10 @@ DHUDMessageFadeOut::DHUDMessageFadeOut (FFont *font, const char *text, float x, // //============================================================================ -void DHUDMessageFadeOut::Serialize (FArchive &arc) +void DHUDMessageFadeOut::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << FadeOutTics; + arc("fadeouttics", FadeOutTics); } //============================================================================ @@ -609,10 +615,10 @@ DHUDMessageFadeInOut::DHUDMessageFadeInOut (FFont *font, const char *text, float // //============================================================================ -void DHUDMessageFadeInOut::Serialize (FArchive &arc) +void DHUDMessageFadeInOut::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << FadeInTics; + arc("fadeintics", FadeInTics); } //============================================================================ @@ -718,10 +724,13 @@ DHUDMessageTypeOnFadeOut::DHUDMessageTypeOnFadeOut (FFont *font, const char *tex // //============================================================================ -void DHUDMessageTypeOnFadeOut::Serialize (FArchive &arc) +void DHUDMessageTypeOnFadeOut::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << TypeOnTime << CurrLine << LineVisible << LineLen; + arc("typeontime", TypeOnTime) + ("currline", CurrLine) + ("linevisible", LineVisible) + ("linelen", LineLen); } //============================================================================ diff --git a/src/g_shared/sbar.h b/src/g_shared/sbar.h index 03901f66fd..6d5b107394 100644 --- a/src/g_shared/sbar.h +++ b/src/g_shared/sbar.h @@ -71,7 +71,7 @@ public: EColorRange textColor, float holdTime); virtual ~DHUDMessage (); - virtual void Serialize (FArchive &arc); + virtual void Serialize(FSerializer &arc); void Draw (int bottom, int visibility); virtual void ResetText (const char *text); @@ -157,7 +157,7 @@ public: DHUDMessageFadeOut (FFont *font, const char *text, float x, float y, int hudwidth, int hudheight, EColorRange textColor, float holdTime, float fadeOutTime); - virtual void Serialize (FArchive &arc); + virtual void Serialize(FSerializer &arc); virtual void DoDraw (int linenum, int x, int y, bool clean, int hudheight); virtual bool Tick (); @@ -176,7 +176,7 @@ public: DHUDMessageFadeInOut (FFont *font, const char *text, float x, float y, int hudwidth, int hudheight, EColorRange textColor, float holdTime, float fadeInTime, float fadeOutTime); - virtual void Serialize (FArchive &arc); + virtual void Serialize(FSerializer &arc); virtual void DoDraw (int linenum, int x, int y, bool clean, int hudheight); virtual bool Tick (); @@ -195,7 +195,7 @@ public: DHUDMessageTypeOnFadeOut (FFont *font, const char *text, float x, float y, int hudwidth, int hudheight, EColorRange textColor, float typeTime, float holdTime, float fadeOutTime); - virtual void Serialize (FArchive &arc); + virtual void Serialize(FSerializer &arc); virtual void DoDraw (int linenum, int x, int y, bool clean, int hudheight); virtual bool Tick (); virtual void ScreenSizeChanged (); @@ -355,7 +355,8 @@ public: static void AddBlend (float r, float g, float b, float a, float v_blend[4]); - virtual void Serialize (FArchive &arc); + // do not make this a DObject Serialize function because it's not used like one! + void SerializeMessages(FSerializer &arc); virtual void Tick (); virtual void Draw (EHudState state); diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index f66ec187da..a3bb76b7f4 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -51,8 +51,9 @@ #include "colormatcher.h" #include "v_palette.h" #include "d_player.h" -#include "farchive.h" +#include "serializer.h" #include "gstrings.h" +#include "r_utility.h" #include "../version.h" @@ -1654,12 +1655,9 @@ void DBaseStatusBar::ReceivedWeapon (AWeapon *weapon) { } -void DBaseStatusBar::Serialize (FArchive &arc) +void DBaseStatusBar::SerializeMessages(FSerializer &arc) { - for (size_t i = 0; i < countof(Messages); ++i) - { - arc << Messages[i]; - } + arc.Array("hudmessages", Messages, 3, true); } void DBaseStatusBar::ScreenSizeChanged () diff --git a/src/g_strife/a_strifeglobal.h b/src/g_strife/a_strifeglobal.h index a91052b52b..9e7f9148e0 100644 --- a/src/g_strife/a_strifeglobal.h +++ b/src/g_strife/a_strifeglobal.h @@ -55,7 +55,8 @@ class ASigil : public AWeapon public: bool HandlePickup (AInventory *item); AInventory *CreateCopy (AActor *other); - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); bool SpecialDropAction (AActor *dropper); static int GiveSigilPiece (AActor *daPlayer); void BeginPlay(); diff --git a/src/g_strife/a_strifestuff.cpp b/src/g_strife/a_strifestuff.cpp index 4533016ec1..ad6bbe133d 100644 --- a/src/g_strife/a_strifestuff.cpp +++ b/src/g_strife/a_strifestuff.cpp @@ -18,7 +18,7 @@ #include "templates.h" #include "d_event.h" #include "v_font.h" -#include "farchive.h" +#include "serializer.h" #include "p_spec.h" #include "portal.h" diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index 24e7fb5c7e..be3c3bf2bf 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -753,10 +753,11 @@ void ASigil::BeginPlay() // //============================================================================ -void ASigil::Serialize (FArchive &arc) +void ASigil::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << NumPieces << DownPieces; + arc("numpieces", NumPieces) + ("downpieces", DownPieces); } //============================================================================ diff --git a/src/gl/dynlights/a_dynlight.cpp b/src/gl/dynlights/a_dynlight.cpp index fcb945f018..36ef1b9483 100644 --- a/src/gl/dynlights/a_dynlight.cpp +++ b/src/gl/dynlights/a_dynlight.cpp @@ -69,6 +69,7 @@ #include "r_utility.h" #include "portal.h" #include "doomstat.h" +#include "serializer.h" #include "gl/renderer/gl_renderer.h" @@ -154,24 +155,31 @@ static FRandom randLight; // // //========================================================================== -void ADynamicLight::Serialize(FArchive &arc) +void ADynamicLight::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << lightflags << lighttype; - arc << m_tickCount << m_currentRadius; - arc << m_Radius[0] << m_Radius[1]; + auto def = static_cast(GetDefault()); + arc("lightflags", lightflags, def->lightflags) + ("lighttype", lighttype, def->lighttype) + ("tickcount", m_tickCount, def->m_tickCount) + ("currentradius", m_currentRadius, def->m_currentRadius) + .Array("radius", m_Radius, def->m_Radius, 2); - if (lighttype == PulseLight) arc << m_lastUpdate << m_cycler; - if (arc.IsLoading()) - { - // The default constructor which is used for creating objects before deserialization will not set this variable. - // It needs to be true for all placed lights. - visibletoplayer = true; - LinkLight(); - } + if (lighttype == PulseLight) + arc("lastupdate", m_lastUpdate, def->m_lastUpdate) + ("cycler", m_cycler, def->m_cycler); } +void ADynamicLight::PostSerialize() +{ + Super::PostSerialize(); + // The default constructor which is used for creating objects before deserialization will not set this variable. + // It needs to be true for all placed lights. + visibletoplayer = true; + LinkLight(); +} + //========================================================================== // // [TS] diff --git a/src/gl/dynlights/gl_dynlight.h b/src/gl/dynlights/gl_dynlight.h index d92bf35c62..84965e3185 100644 --- a/src/gl/dynlights/gl_dynlight.h +++ b/src/gl/dynlights/gl_dynlight.h @@ -32,7 +32,7 @@ EXTERN_CVAR(Bool, gl_lights) EXTERN_CVAR(Bool, gl_attachedlights) class ADynamicLight; -class FArchive; +class FSerializer; @@ -93,7 +93,8 @@ class ADynamicLight : public AActor DECLARE_CLASS (ADynamicLight, AActor) public: virtual void Tick(); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); + void PostSerialize(); BYTE GetRed() const { return args[LIGHT_RED]; } BYTE GetGreen() const { return args[LIGHT_GREEN]; } BYTE GetBlue() const { return args[LIGHT_BLUE]; } diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index b954085214..71c52474a1 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -169,7 +169,7 @@ public: void RenderScreenQuad(); void SetFixedColormap (player_t *player); - void WriteSavePic (player_t *player, FILE *file, int width, int height); + void WriteSavePic (player_t *player, FileWriter *file, int width, int height); void EndDrawScene(sector_t * viewsector); void UpdateCameraExposure(); void BloomScene(); diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 528925b7a2..0c76b4bb1b 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -43,6 +43,7 @@ #include "a_hexenglobal.h" #include "p_local.h" #include "gl/gl_functions.h" +#include "serializer.h" #include "gl/dynlights/gl_lightbuffer.h" #include "gl/system/gl_interface.h" @@ -921,7 +922,7 @@ void FGLRenderer::RenderView (player_t* player) // //=========================================================================== -void FGLRenderer::WriteSavePic (player_t *player, FILE *file, int width, int height) +void FGLRenderer::WriteSavePic (player_t *player, FileWriter *file, int width, int height) { GL_IRECT bounds; @@ -972,10 +973,10 @@ struct FGLInterface : public FRenderer void PrecacheSprite(FTexture *tex, SpriteHits &hits); void Precache(BYTE *texhitlist, TMap &actorhitlist) override; void RenderView(player_t *player) override; - void WriteSavePic (player_t *player, FILE *file, int width, int height) override; + void WriteSavePic (player_t *player, FileWriter *file, int width, int height) override; void StateChanged(AActor *actor) override; - void StartSerialize(FArchive &arc) override; - void EndSerialize(FArchive &arc) override; + void StartSerialize(FSerializer &arc) override; + void EndSerialize(FSerializer &arc) override; void RenderTextureView (FCanvasTexture *self, AActor *viewpoint, int fov) override; sector_t *FakeFlat(sector_t *sec, sector_t *tempsec, int *floorlightlevel, int *ceilinglightlevel, bool back) override; void SetFogParams(int _fogdensity, PalEntry _outsidefogcolor, int _outsidefogdensity, int _skyfog) override; @@ -1195,20 +1196,22 @@ void FGLInterface::StateChanged(AActor *actor) // //=========================================================================== -void FGLInterface::StartSerialize(FArchive &arc) +void FGLInterface::StartSerialize(FSerializer &arc) { gl_DeleteAllAttachedLights(); + if (arc.BeginObject("glinfo")) + { + arc("fogdensity", fogdensity) + ("outsidefogdensity", outsidefogdensity) + ("skyfog", skyfog) + .EndObject(); + } } -void gl_SerializeGlobals(FArchive &arc) -{ - arc << fogdensity << outsidefogdensity << skyfog; -} - -void FGLInterface::EndSerialize(FArchive &arc) +void FGLInterface::EndSerialize(FSerializer &arc) { gl_RecreateAllAttachedLights(); - if (arc.IsLoading()) gl_InitPortals(); + if (arc.isReading()) gl_InitPortals(); } //=========================================================================== @@ -1244,7 +1247,7 @@ void FGLInterface::ClearBuffer(int color) // //=========================================================================== -void FGLInterface::WriteSavePic (player_t *player, FILE *file, int width, int height) +void FGLInterface::WriteSavePic (player_t *player, FileWriter *file, int width, int height) { GLRenderer->WriteSavePic(player, file, width, height); } diff --git a/src/gl/system/gl_framebuffer.cpp b/src/gl/system/gl_framebuffer.cpp index 1ca2f7eddc..94eba0817c 100644 --- a/src/gl/system/gl_framebuffer.cpp +++ b/src/gl/system/gl_framebuffer.cpp @@ -36,7 +36,6 @@ #include "vectors.h" #include "v_palette.h" #include "templates.h" -#include "farchive.h" #include "gl/system/gl_interface.h" #include "gl/system/gl_framebuffer.h" diff --git a/src/gl/utility/gl_cycler.cpp b/src/gl/utility/gl_cycler.cpp index a12cb48510..b6d160925c 100644 --- a/src/gl/utility/gl_cycler.cpp +++ b/src/gl/utility/gl_cycler.cpp @@ -34,19 +34,30 @@ */ #include +#include "serializer.h" #include "gl/utility/gl_cycler.h" //========================================================================== // -// +// This will never be called with a null-def, so don't bother with that case. // //========================================================================== -void FCycler::Serialize(FArchive & arc) +FSerializer &Serialize(FSerializer &arc, const char *key, FCycler &c, FCycler *def) { - arc << m_start << m_end << m_current - << m_time << m_cycle << m_increment << m_shouldCycle - << m_cycleType; + if (arc.BeginObject(key)) + { + arc("start", c.m_start, def->m_start) + ("end", c.m_end, def->m_end) + ("current", c.m_current, def->m_current) + ("time", c.m_time, def->m_time) + ("cycle", c.m_cycle, def->m_cycle) + ("increment", c.m_increment, def->m_increment) + ("shouldcycle", c.m_shouldCycle, def->m_shouldCycle) + .Enum("type", c.m_cycleType) + .EndObject(); + } + return arc; } //========================================================================== diff --git a/src/gl/utility/gl_cycler.h b/src/gl/utility/gl_cycler.h index 8a6b55581f..42effce345 100644 --- a/src/gl/utility/gl_cycler.h +++ b/src/gl/utility/gl_cycler.h @@ -1,28 +1,24 @@ #ifndef __GL_CYCLER_H #define __GL_CYCLER_H -#include "farchive.h" +class FSerializer; -typedef enum +enum CycleType { - CYCLE_Linear, - CYCLE_Sin, - CYCLE_Cos, - CYCLE_SawTooth, - CYCLE_Square -} CycleType; - -inline FArchive &operator<< (FArchive &arc, CycleType &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (CycleType)val; - return arc; -} + CYCLE_Linear, + CYCLE_Sin, + CYCLE_Cos, + CYCLE_SawTooth, + CYCLE_Square +}; +class FCycler; +FSerializer &Serialize(FSerializer &arc, const char *key, FCycler &c, FCycler *def); class FCycler { + friend FSerializer &Serialize(FSerializer &arc, const char *key, FCycler &c, FCycler *def); + public: FCycler(); void Update(float diff); @@ -33,7 +29,6 @@ public: inline operator float () const { return m_current; } - void Serialize(FArchive & arc); protected: float m_start, m_end, m_current; float m_time, m_cycle; @@ -42,11 +37,5 @@ protected: CycleType m_cycleType; }; -inline FArchive &operator<< (FArchive &arc, FCycler &type) -{ - type.Serialize(arc); - return arc; -} - #endif diff --git a/src/info.cpp b/src/info.cpp index 6eeca1186b..79c8e4268e 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -375,6 +375,9 @@ void PClassActor::InitializeNativeDefaults() else { memset (Defaults, 0, Size); + // Non-DECORATE properties that must be set. + ((AActor*)Defaults)->DamageMultiply = 1.; // fixme: Make this a DECORATE property. + ((AActor*)Defaults)->ConversationRoot = -1; } } diff --git a/src/info.h b/src/info.h index 6ced755a3f..0e49e1f8fb 100644 --- a/src/info.h +++ b/src/info.h @@ -52,7 +52,6 @@ struct Baggage; class FScanner; struct FActorInfo; -class FArchive; class FIntCVar; enum EStateType @@ -168,10 +167,6 @@ struct FStateLabels void Destroy(); // intentionally not a destructor! }; - - -FArchive &operator<< (FArchive &arc, FState *&state); - #include "gametype.h" struct DmgFactors : public TMap diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index e78174a550..7c110a52c9 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -45,7 +45,7 @@ #include "d_net.h" #include "d_dehacked.h" #include "gi.h" -#include "farchive.h" +#include "serializer.h" #include "r_utility.h" #include "a_morph.h" @@ -1026,10 +1026,10 @@ public: } // You'll probably never be able to catch this in a save game, but // just in case, add a proper serializer. - void Serialize(FArchive &arc) + void Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << Pawn; + arc("pawn", Pawn); } }; diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 87f61f2539..824fe0533d 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -449,10 +449,15 @@ struct pcx_t }; +inline void putc(unsigned char chr, FileWriter *file) +{ + file->Write(&chr, 1); +} + // // WritePCXfile // -void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette, +void WritePCXfile (FileWriter *file, const BYTE *buffer, const PalEntry *palette, ESSType color_type, int width, int height, int pitch) { BYTE temprow[MAXWIDTH * 3]; @@ -480,7 +485,7 @@ void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette, pcx.palette_type = 1; // not a grey scale memset (pcx.filler, 0, sizeof(pcx.filler)); - fwrite (&pcx, 128, 1, file); + file->Write(&pcx, 128); bytes_per_row_minus_one = ((color_type == SS_PAL) ? width : width * 3) - 1; @@ -593,7 +598,7 @@ void WritePCXfile (FILE *file, const BYTE *buffer, const PalEntry *palette, // // WritePNGfile // -void WritePNGfile (FILE *file, const BYTE *buffer, const PalEntry *palette, +void WritePNGfile (FileWriter *file, const BYTE *buffer, const PalEntry *palette, ESSType color_type, int width, int height, int pitch) { char software[100]; @@ -655,7 +660,7 @@ static bool FindFreeName (FString &fullname, const char *extension) void M_ScreenShot (const char *filename) { - FILE *file; + FileWriter *file; FString autoname; bool writepcx = (stricmp (screenshot_type, "pcx") == 0); // PNG is the default @@ -709,7 +714,7 @@ void M_ScreenShot (const char *filename) { screen->GetFlashedPalette(palette); } - file = fopen (autoname, "wb"); + file = FileWriter::Open(autoname); if (file == NULL) { Printf ("Could not open %s\n", autoname.GetChars()); @@ -726,7 +731,7 @@ void M_ScreenShot (const char *filename) WritePNGfile(file, buffer, palette, color_type, screen->GetWidth(), screen->GetHeight(), pitch); } - fclose(file); + delete file; screen->ReleaseScreenshotBuffer(); if (!screenshot_quiet) diff --git a/src/m_png.cpp b/src/m_png.cpp index 22a9a657d1..b122212a57 100644 --- a/src/m_png.cpp +++ b/src/m_png.cpp @@ -80,7 +80,7 @@ PNGHandle::PNGHandle (FILE *file) : File(0), bDeleteFilePtr(true), ChunkPt(0) File = new FileReader(file); } -PNGHandle::PNGHandle (FileReader *file) : File(file), bDeleteFilePtr(false), ChunkPt(0) {} +PNGHandle::PNGHandle (FileReader *file, bool takereader) : File(file), bDeleteFilePtr(takereader), ChunkPt(0) {} PNGHandle::~PNGHandle () { for (unsigned int i = 0; i < TextChunks.Size(); ++i) @@ -101,7 +101,7 @@ PNGHandle::~PNGHandle () static inline void MakeChunk (void *where, DWORD type, size_t len); static inline void StuffPalette (const PalEntry *from, BYTE *to); -static bool WriteIDAT (FILE *file, const BYTE *data, int len); +static bool WriteIDAT (FileWriter *file, const BYTE *data, int len); static void UnfilterRow (int width, BYTE *dest, BYTE *stream, BYTE *prev, int bpp); static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout, bool grayscale); @@ -131,7 +131,7 @@ CVAR(Float, png_gamma, 0.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // //========================================================================== -bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *palette, +bool M_CreatePNG (FileWriter *file, const BYTE *buffer, const PalEntry *palette, ESSType color_type, int width, int height, int pitch) { BYTE work[8 + // signature @@ -171,7 +171,7 @@ bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *palette, work_len = sizeof(work) - (12+256*3); } - if (fwrite (work, 1, work_len, file) != work_len) + if (file->Write (work, work_len) != work_len) return false; return M_SaveBitmap (buffer, color_type, width, height, pitch, file); @@ -185,7 +185,7 @@ bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *palette, // //========================================================================== -bool M_CreateDummyPNG (FILE *file) +bool M_CreateDummyPNG (FileWriter *file) { static const BYTE dummyPNG[] = { @@ -195,7 +195,7 @@ bool M_CreateDummyPNG (FILE *file) 0,0,0,10,'I','D','A','T', 104,222,99,96,0,0,0,2,0,1,0x9f,0x65,0x0e,0x18 }; - return fwrite (dummyPNG, 1, sizeof(dummyPNG), file) == sizeof(dummyPNG); + return file->Write (dummyPNG, sizeof(dummyPNG)) == sizeof(dummyPNG); } @@ -207,10 +207,10 @@ bool M_CreateDummyPNG (FILE *file) // //========================================================================== -bool M_FinishPNG (FILE *file) +bool M_FinishPNG (FileWriter *file) { static const BYTE iend[12] = { 0,0,0,0,73,69,78,68,174,66,96,130 }; - return fwrite (iend, 1, 12, file) == 12; + return file->Write (iend, 12) == 12; } //========================================================================== @@ -221,13 +221,13 @@ bool M_FinishPNG (FILE *file) // //========================================================================== -bool M_AppendPNGChunk (FILE *file, DWORD chunkID, const BYTE *chunkData, DWORD len) +bool M_AppendPNGChunk (FileWriter *file, DWORD chunkID, const BYTE *chunkData, DWORD len) { DWORD head[2] = { BigLong((unsigned int)len), chunkID }; DWORD crc; - if (fwrite (head, 1, 8, file) == 8 && - (len == 0 || fwrite (chunkData, 1, len, file) == len)) + if (file->Write (head, 8) == 8 && + (len == 0 || file->Write (chunkData, len) == len)) { crc = CalcCRC32 ((BYTE *)&head[1], 4); if (len != 0) @@ -235,7 +235,7 @@ bool M_AppendPNGChunk (FILE *file, DWORD chunkID, const BYTE *chunkData, DWORD l crc = AddCRC32 (crc, chunkData, len); } crc = BigLong((unsigned int)crc); - return fwrite (&crc, 1, 4, file) == 4; + return file->Write (&crc, 4) == 4; } return false; } @@ -248,7 +248,7 @@ bool M_AppendPNGChunk (FILE *file, DWORD chunkID, const BYTE *chunkData, DWORD l // //========================================================================== -bool M_AppendPNGText (FILE *file, const char *keyword, const char *text) +bool M_AppendPNGText (FileWriter *file, const char *keyword, const char *text) { struct { DWORD len, id; char key[80]; } head; int len = (int)strlen (text); @@ -261,8 +261,8 @@ bool M_AppendPNGText (FILE *file, const char *keyword, const char *text) strncpy (head.key, keyword, keylen); head.key[keylen] = 0; - if ((int)fwrite (&head, 1, keylen + 9, file) == keylen + 9 && - (int)fwrite (text, 1, len, file) == len) + if ((int)file->Write (&head, keylen + 9) == keylen + 9 && + (int)file->Write (text, len) == len) { crc = CalcCRC32 ((BYTE *)&head+4, keylen + 5); if (len != 0) @@ -270,7 +270,7 @@ bool M_AppendPNGText (FILE *file, const char *keyword, const char *text) crc = AddCRC32 (crc, (BYTE *)text, len); } crc = BigLong((unsigned int)crc); - return fwrite (&crc, 1, 4, file) == 4; + return file->Write (&crc, 4) == 4; } return false; } @@ -374,15 +374,14 @@ bool M_GetPNGText (PNGHandle *png, const char *keyword, char *buffer, size_t buf // //========================================================================== -PNGHandle *M_VerifyPNG (FILE *file) +PNGHandle *M_VerifyPNG (FileReader *filer, bool takereader) { PNGHandle::Chunk chunk; - FileReader *filer; PNGHandle *png; DWORD data[2]; bool sawIDAT = false; - if (fread (&data, 1, 8, file) != 8) + if (filer->Read(&data, 8) != 8) { return NULL; } @@ -390,7 +389,7 @@ PNGHandle *M_VerifyPNG (FILE *file) { // Does not have PNG signature return NULL; } - if (fread (&data, 1, 8, file) != 8) + if (filer->Read (&data, 8) != 8) { return NULL; } @@ -400,8 +399,7 @@ PNGHandle *M_VerifyPNG (FILE *file) } // It looks like a PNG so far, so start creating a PNGHandle for it - png = new PNGHandle (file); - filer = png->File; + png = new PNGHandle (filer, takereader); chunk.ID = data[1]; chunk.Offset = 16; chunk.Size = BigLong((unsigned int)data[0]); @@ -430,7 +428,7 @@ PNGHandle *M_VerifyPNG (FILE *file) sawIDAT = true; } chunk.ID = data[1]; - chunk.Offset = ftell (file); + chunk.Offset = filer->Tell(); chunk.Size = BigLong((unsigned int)data[0]); png->Chunks.Push (chunk); @@ -454,6 +452,12 @@ PNGHandle *M_VerifyPNG (FILE *file) return NULL; } +PNGHandle *M_VerifyPNG(FILE *file) +{ + FileReader *fr = new FileReader(file); + return M_VerifyPNG(fr, true); +} + //========================================================================== // // M_FreePNG @@ -900,7 +904,7 @@ static int SelectFilter(Byte row[5][1 + MAXWIDTH*3], Byte prior[MAXWIDTH*3], int // //========================================================================== -bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FILE *file) +bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FileWriter *file) { #if USE_FILTER_HEURISTIC Byte prior[MAXWIDTH*3]; @@ -1040,7 +1044,7 @@ bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, i // //========================================================================== -static bool WriteIDAT (FILE *file, const BYTE *data, int len) +static bool WriteIDAT (FileWriter *file, const BYTE *data, int len) { DWORD foo[2], crc; @@ -1049,9 +1053,9 @@ static bool WriteIDAT (FILE *file, const BYTE *data, int len) crc = CalcCRC32 ((BYTE *)&foo[1], 4); crc = BigLong ((unsigned int)AddCRC32 (crc, data, len)); - if (fwrite (foo, 1, 8, file) != 8 || - fwrite (data, 1, len, file) != (size_t)len || - fwrite (&crc, 1, 4, file) != 4) + if (file->Write (foo, 8) != 8 || + file->Write (data, len) != (size_t)len || + file->Write (&crc, 4) != 4) { return false; } diff --git a/src/m_png.h b/src/m_png.h index 6434031d28..9fb9eeeffd 100644 --- a/src/m_png.h +++ b/src/m_png.h @@ -36,6 +36,7 @@ #include #include "doomtype.h" #include "v_video.h" +#include "files.h" // PNG Writing -------------------------------------------------------------- @@ -43,22 +44,22 @@ // The passed file should be a newly created file. // This function writes the PNG signature and the IHDR, gAMA, PLTE, and IDAT // chunks. -bool M_CreatePNG (FILE *file, const BYTE *buffer, const PalEntry *pal, +bool M_CreatePNG (FileWriter *file, const BYTE *buffer, const PalEntry *pal, ESSType color_type, int width, int height, int pitch); // Creates a grayscale 1x1 PNG file. Used for savegames without savepics. -bool M_CreateDummyPNG (FILE *file); +bool M_CreateDummyPNG (FileWriter *file); // Appends any chunk to a PNG file started with M_CreatePNG. -bool M_AppendPNGChunk (FILE *file, DWORD chunkID, const BYTE *chunkData, DWORD len); +bool M_AppendPNGChunk (FileWriter *file, DWORD chunkID, const BYTE *chunkData, DWORD len); // Adds a tEXt chunk to a PNG file started with M_CreatePNG. -bool M_AppendPNGText (FILE *file, const char *keyword, const char *text); +bool M_AppendPNGText (FileWriter *file, const char *keyword, const char *text); // Appends the IEND chunk to a PNG file. -bool M_FinishPNG (FILE *file); +bool M_FinishPNG (FileWriter *file); -bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FILE *file); +bool M_SaveBitmap(const BYTE *from, ESSType color_type, int width, int height, int pitch, FileWriter *file); // PNG Reading -------------------------------------------------------------- @@ -79,7 +80,7 @@ struct PNGHandle unsigned int ChunkPt; PNGHandle(FILE *file); - PNGHandle(FileReader *file); + PNGHandle(FileReader *file, bool takereader = false); ~PNGHandle(); }; @@ -87,7 +88,7 @@ struct PNGHandle // the signature, but also checking for the IEND chunk. CRC checking of // each chunk is not done. If it is valid, you get a PNGHandle to pass to // the following functions. -PNGHandle *M_VerifyPNG (FileReader *file); +PNGHandle *M_VerifyPNG (FileReader *file, bool takereader = false); PNGHandle *M_VerifyPNG (FILE *file); // Finds a chunk in a PNG file. The file pointer will be positioned at the diff --git a/src/m_random.cpp b/src/m_random.cpp index c0516e1f5a..e0a55d0959 100644 --- a/src/m_random.cpp +++ b/src/m_random.cpp @@ -61,7 +61,7 @@ #include "doomstat.h" #include "m_random.h" -#include "farchive.h" +#include "serializer.h" #include "b_bot.h" #include "m_png.h" #include "m_crc32.h" @@ -291,24 +291,29 @@ DWORD FRandom::StaticSumSeeds () // //========================================================================== -void FRandom::StaticWriteRNGState (FILE *file) +void FRandom::StaticWriteRNGState (FSerializer &arc) { FRandom *rng; - FPNGChunkArchive arc (file, RAND_ID); - arc << rngseed; + arc("rngseed", rngseed); - for (rng = FRandom::RNGList; rng != NULL; rng = rng->Next) + if (arc.BeginArray("rngs")) { - // Only write those RNGs that have names - if (rng->NameCRC != 0) + for (rng = FRandom::RNGList; rng != NULL; rng = rng->Next) { - arc << rng->NameCRC << rng->idx; - for (int i = 0; i < SFMT::N32; ++i) + // Only write those RNGs that have names + if (rng->NameCRC != 0) { - arc << rng->sfmt.u[i]; + if (arc.BeginObject(nullptr)) + { + arc("crc", rng->NameCRC) + ("index", rng->idx) + .Array("u", rng->sfmt.u, SFMT::N32) + .EndObject(); + } } } + arc.EndArray(); } } @@ -321,51 +326,35 @@ void FRandom::StaticWriteRNGState (FILE *file) // //========================================================================== -void FRandom::StaticReadRNGState (PNGHandle *png) +void FRandom::StaticReadRNGState(FSerializer &arc) { FRandom *rng; - size_t len = M_FindPNGChunk (png, RAND_ID); - - if (len != 0) + arc("rngseed", rngseed); + if (arc.BeginArray("rngs")) { - const size_t sizeof_rng = sizeof(rng->NameCRC) + sizeof(rng->idx) + sizeof(rng->sfmt.u); - const int rngcount = (int)((len-4) / sizeof_rng); - int i; - DWORD crc; + int count = arc.ArraySize(); - FPNGChunkArchive arc (png->File->GetFile(), RAND_ID, len); - - arc << rngseed; - FRandom::StaticClearRandom (); - - for (i = rngcount; i; --i) + for (int i = 0; i < count; i++) { - arc << crc; - for (rng = FRandom::RNGList; rng != NULL; rng = rng->Next) + if (arc.BeginObject(nullptr)) { - if (rng->NameCRC == crc) + uint32_t crc; + arc("crc", crc); + + for (rng = FRandom::RNGList; rng != NULL; rng = rng->Next) { - arc << rng->idx; - for (int i = 0; i < SFMT::N32; ++i) + if (rng->NameCRC == crc) { - arc << rng->sfmt.u[i]; + arc("index", rng->idx) + .Array("u", rng->sfmt.u, SFMT::N32); + break; } - break; - } - } - if (rng == NULL) - { // The RNG was removed. Skip it. - int idx; - DWORD sfmt; - arc << idx; - for (int i = 0; i < SFMT::N32; ++i) - { - arc << sfmt; } + arc.EndObject(); } } - png->File->ResetFilePtr(); + arc.EndArray(); } } diff --git a/src/m_random.h b/src/m_random.h index fab21b4eee..b3e0f3b283 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -39,7 +39,7 @@ #include "basictypes.h" #include "sfmt/SFMT.h" -struct PNGHandle; +class FSerializer; class FRandom { @@ -174,8 +174,8 @@ public: // Static interface static void StaticClearRandom (); static DWORD StaticSumSeeds (); - static void StaticReadRNGState (PNGHandle *png); - static void StaticWriteRNGState (FILE *file); + static void StaticReadRNGState (FSerializer &arc); + static void StaticWriteRNGState (FSerializer &file); static FRandom *StaticFindRNG(const char *name); #ifndef NDEBUG diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp index f32146d50e..99c34ab3e4 100644 --- a/src/menu/loadsavemenu.cpp +++ b/src/menu/loadsavemenu.cpp @@ -46,6 +46,8 @@ #include "doomstat.h" #include "gi.h" #include "d_gui.h" +#include "serializer.h" +#include "resourcefiles/resourcefile.h" @@ -86,6 +88,10 @@ protected: int commentRight; int commentBottom; + // this needs to be kept in memory so that the texture can access it when it needs to. + FileReader *currentSavePic; + TArray SavePicData; + static int InsertSaveNode (FSaveGameNode *node); static void ReadSaveStrings (); @@ -213,7 +219,7 @@ void DLoadSaveMenu::ReadSaveStrings () LastSaved = LastAccessed = -1; quickSaveSlot = NULL; - filter = G_BuildSaveName ("*.zds", -1); + filter = G_BuildSaveName ("*." SAVEGAME_EXT, -1); filefirst = I_FindFirst (filter.GetChars(), &c_file); if (filefirst != ((void *)(-1))) { @@ -221,103 +227,141 @@ void DLoadSaveMenu::ReadSaveStrings () { // I_FindName only returns the file's name and not its full path FString filepath = G_BuildSaveName (I_FindName(&c_file), -1); - FILE *file = fopen (filepath, "rb"); - if (file != NULL) + FResourceFile *savegame = FResourceFile::OpenResourceFile(filepath, nullptr, true, true); + if (savegame != nullptr) { - PNGHandle *png; - char sig[16]; - char title[SAVESTRINGSIZE+1]; - bool oldVer = true; - bool addIt = false; + bool oldVer = false; bool missing = false; - - // ZDoom 1.23 betas 21-33 have the savesig first. - // Earlier versions have the savesig second. - // Later versions have the savegame encapsulated inside a PNG. - // - // Old savegame versions are always added to the menu so - // the user can easily delete them if desired. - - title[SAVESTRINGSIZE] = 0; - - if (NULL != (png = M_VerifyPNG (file))) + FResourceLump *info = savegame->FindLump("info.json"); + if (info == nullptr) { - char *ver = M_GetPNGText (png, "ZDoom Save Version"); - char *engine = M_GetPNGText (png, "Engine"); - if (ver != NULL) - { - if (!M_GetPNGText (png, "Title", title, SAVESTRINGSIZE)) - { - strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE); - } - if (strncmp (ver, SAVESIG, 9) == 0 && - atoi (ver+9) >= MINSAVEVER && - engine != NULL) - { - // Was saved with a compatible ZDoom version, - // so check if it's for the current game. - // If it is, add it. Otherwise, ignore it. - char *iwad = M_GetPNGText (png, "Game WAD"); - if (iwad != NULL) - { - if (stricmp (iwad, Wads.GetWadName (FWadCollection::IWAD_FILENUM)) == 0) - { - addIt = true; - oldVer = false; - missing = !G_CheckSaveGameWads (png, false); - } - delete[] iwad; - } - } - else - { // An old version - addIt = true; - } - delete[] ver; - } - if (engine != NULL) - { - delete[] engine; - } - delete png; + // savegame info not found. This is not a savegame so leave it alone. + delete savegame; + continue; } - else + void *data = info->CacheLump(); + FSerializer arc; + if (arc.OpenReader((const char *)data, info->LumpSize)) { - fseek (file, 0, SEEK_SET); - if (fread (sig, 1, 16, file) == 16) + int savever = 0; + FString engine; + FString iwad; + FString title; + + arc("Save Version", savever); + arc("Engine", engine); + arc("Game WAD", iwad); + arc("Title", title); + + if (engine.Compare(GAMESIG) != 0 || savever > SAVEVER) { - - if (strncmp (sig, "ZDOOMSAVE", 9) == 0) - { - if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) - { - addIt = true; - } - } - else - { - memcpy (title, sig, 16); - if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && - fread (sig, 1, 16, file) == 16 && - strncmp (sig, "ZDOOMSAVE", 9) == 0) - { - addIt = true; - } - } + // different engine or newer version: + // not our business. Leave it alone. + delete savegame; + continue; + } + + if (savever < MINSAVEVER) + { + // old, incompatible savegame. List as not usable. + oldVer = true; + } + else if (iwad.CompareNoCase(Wads.GetWadName(FWadCollection::IWAD_FILENUM)) == 0) + { + missing = !G_CheckSaveGameWads(arc, false); + } + else + { + // different game. Skip this. + delete savegame; + continue; } - } - if (addIt) - { FSaveGameNode *node = new FSaveGameNode; node->Filename = filepath; node->bOldVersion = oldVer; node->bMissingWads = missing; - memcpy (node->Title, title, SAVESTRINGSIZE); - InsertSaveNode (node); + strncpy(node->Title, title.GetChars(), SAVESTRINGSIZE); + InsertSaveNode(node); + delete savegame; + } + + } + else // check for old formats. + { + FILE *file = fopen (filepath, "rb"); + if (file != NULL) + { + PNGHandle *png; + char sig[16]; + char title[SAVESTRINGSIZE+1]; + bool oldVer = true; + bool addIt = false; + bool missing = false; + + // ZDoom 1.23 betas 21-33 have the savesig first. + // Earlier versions have the savesig second. + // Later versions have the savegame encapsulated inside a PNG. + // + // Old savegame versions are always added to the menu so + // the user can easily delete them if desired. + + title[SAVESTRINGSIZE] = 0; + + + if (NULL != (png = M_VerifyPNG (file))) + { + char *ver = M_GetPNGText (png, "ZDoom Save Version"); + if (ver != NULL) + { + // An old version + if (!M_GetPNGText(png, "Title", title, SAVESTRINGSIZE)) + { + strncpy(title, I_FindName(&c_file), SAVESTRINGSIZE); + } + addIt = true; + delete[] ver; + } + delete png; + } + else + { + fseek (file, 0, SEEK_SET); + if (fread (sig, 1, 16, file) == 16) + { + + if (strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) + { + addIt = true; + } + } + else + { + memcpy (title, sig, 16); + if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && + fread (sig, 1, 16, file) == 16 && + strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + addIt = true; + } + } + } + } + + if (addIt) + { + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = true; + node->bMissingWads = false; + memcpy (node->Title, title, SAVESTRINGSIZE); + InsertSaveNode (node); + } + fclose (file); } - fclose (file); } } while (I_FindNext (filefirst, &c_file) == 0); I_FindClose (filefirst); @@ -407,6 +451,7 @@ DLoadSaveMenu::DLoadSaveMenu(DMenu *parent, FListMenuDescriptor *desc) listboxHeight = listboxRows * rowHeight + 1; listboxRight = listboxLeft + listboxWidth; listboxBottom = listboxTop + listboxHeight; + currentSavePic = nullptr; commentLeft = savepicLeft; commentTop = savepicTop + savepicHeight + 16; @@ -418,6 +463,8 @@ DLoadSaveMenu::DLoadSaveMenu(DMenu *parent, FListMenuDescriptor *desc) void DLoadSaveMenu::Destroy() { + if (currentSavePic != nullptr) delete currentSavePic; + currentSavePic = nullptr; ClearSaveStuff (); } @@ -429,17 +476,23 @@ void DLoadSaveMenu::Destroy() void DLoadSaveMenu::UnloadSaveData () { - if (SavePic != NULL) + if (SavePic != nullptr) { delete SavePic; } - if (SaveComment != NULL) + if (SaveComment != nullptr) { V_FreeBrokenLines (SaveComment); } + if (currentSavePic != nullptr) + { + delete currentSavePic; + } - SavePic = NULL; - SaveComment = NULL; + SavePic = nullptr; + SaveComment = nullptr; + currentSavePic = nullptr; + SavePicData.Clear(); } //============================================================================= @@ -465,8 +518,7 @@ void DLoadSaveMenu::ClearSaveStuff () void DLoadSaveMenu::ExtractSaveData (int index) { - FILE *file; - PNGHandle *png; + FResourceFile *resf; FSaveGameNode *node; UnloadSaveData (); @@ -475,66 +527,61 @@ void DLoadSaveMenu::ExtractSaveData (int index) (node = SaveGames[index]) && !node->Filename.IsEmpty() && !node->bOldVersion && - (file = fopen (node->Filename.GetChars(), "rb")) != NULL) + (resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), nullptr, true)) != nullptr) { - if (NULL != (png = M_VerifyPNG (file))) + FResourceLump *info = resf->FindLump("info.json"); + if (info == nullptr) { - char *time, *pcomment, *comment; - size_t commentlen, totallen, timelen; + // this should not happen because the file has already been verified. + return; + } + void *data = info->CacheLump(); + FSerializer arc; + if (arc.OpenReader((const char *)data, info->LumpSize)) + { + FString time, pcomment, comment; - // Extract comment - time = M_GetPNGText (png, "Creation Time"); - pcomment = M_GetPNGText (png, "Comment"); - if (pcomment != NULL) - { - commentlen = strlen (pcomment); - } - else - { - commentlen = 0; - } - if (time != NULL) - { - timelen = strlen (time); - totallen = timelen + commentlen + 3; - } - else - { - timelen = 0; - totallen = commentlen + 1; - } - if (totallen != 0) - { - comment = new char[totallen]; + arc("Creation Time", time); + arc("Comment", pcomment); - if (timelen) - { - memcpy (comment, time, timelen); - comment[timelen] = '\n'; - comment[timelen+1] = '\n'; - timelen += 2; - } - if (commentlen) - { - memcpy (comment + timelen, pcomment, commentlen); - } - comment[timelen+commentlen] = 0; - SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment); - delete[] comment; - delete[] time; - delete[] pcomment; - } + comment = time; + if (time.Len() > 0) comment += "\n\n"; + comment += pcomment; + + SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment.GetChars()); // Extract pic - SavePic = PNGTexture_CreateFromFile(png, node->Filename); - delete png; - if (SavePic->GetWidth() == 1 && SavePic->GetHeight() == 1) + FResourceLump *pic = resf->FindLump("savepic.png"); + if (pic != nullptr) { - delete SavePic; - SavePic = NULL; + FileReader *reader = pic->NewReader(); + if (reader != nullptr) + { + // copy to a memory buffer which gets accessed through a memory reader and PNGHandle. + // We cannot use the actual lump as backing for the texture because that requires keeping the + // savegame file open. + SavePicData.Resize(pic->LumpSize); + reader->Read(&SavePicData[0], pic->LumpSize); + reader = new MemoryReader(&SavePicData[0], SavePicData.Size()); + PNGHandle *png = M_VerifyPNG(reader); + if (png != nullptr) + { + SavePic = PNGTexture_CreateFromFile(png, node->Filename); + currentSavePic = reader; // must be kept so that the texture can read from it. + delete png; + if (SavePic->GetWidth() == 1 && SavePic->GetHeight() == 1) + { + delete SavePic; + SavePic = nullptr; + delete currentSavePic; + currentSavePic = nullptr; + SavePicData.Clear(); + } + } + } } } - fclose (file); + delete resf; } } diff --git a/src/p_acs.cpp b/src/p_acs.cpp index c85cc65d6b..fefe5cd8f8 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -74,7 +74,7 @@ #include "p_setup.h" #include "po_man.h" #include "actorptrselect.h" -#include "farchive.h" +#include "serializer.h" #include "decallib.h" #include "p_terrain.h" #include "version.h" @@ -82,6 +82,7 @@ #include "r_utility.h" #include "a_morph.h" #include "i_music.h" +#include "serializer.h" #include "g_shared/a_pickups.h" @@ -720,47 +721,45 @@ void ACSStringPool::FindFirstFreeEntry(unsigned base) // //============================================================================ -void ACSStringPool::ReadStrings(PNGHandle *png, DWORD id) +void ACSStringPool::ReadStrings(FSerializer &file, const char *key) { Clear(); - size_t len = M_FindPNGChunk(png, id); - if (len != 0) + if (file.BeginObject(key)) { - FPNGChunkArchive arc(png->File->GetFile(), id, len); - int32_t i, j, poolsize; - unsigned int h, bucketnum; - char *str = NULL; - - arc << poolsize; + int poolsize = 0; + file("poolsize", poolsize); Pool.Resize(poolsize); - i = 0; - j = arc.ReadCount(); - while (j >= 0) + for (auto &p : Pool) { - // Mark skipped entries as free - for (; i < j; ++i) + p.Next = FREE_ENTRY; + p.LockCount = 0; + } + if (file.BeginArray("pool")) + { + int j = file.ArraySize(); + for (int i = 0; i < j; i++) { - Pool[i].Next = FREE_ENTRY; - Pool[i].LockCount = 0; + if (file.BeginObject(nullptr)) + { + unsigned ii = UINT_MAX; + file("index", ii); + if (ii < Pool.Size()) + { + file("string", Pool[ii].Str) + ("lockcount", Pool[ii].LockCount); + + unsigned h = SuperFastHash(Pool[ii].Str, Pool[ii].Str.Len()); + unsigned bucketnum = h % NUM_BUCKETS; + Pool[ii].Hash = h; + Pool[ii].Next = PoolBuckets[bucketnum]; + PoolBuckets[bucketnum] = i; + } + file.EndObject(); + } } - arc << str; - h = SuperFastHash(str, strlen(str)); - bucketnum = h % NUM_BUCKETS; - Pool[i].Str = str; - Pool[i].Hash = h; - Pool[i].LockCount = arc.ReadCount(); - Pool[i].Next = PoolBuckets[bucketnum]; - PoolBuckets[bucketnum] = i; - i++; - j = arc.ReadCount(); } - if (str != NULL) - { - delete[] str; - } - FindFirstFreeEntry(0); } } @@ -768,11 +767,11 @@ void ACSStringPool::ReadStrings(PNGHandle *png, DWORD id) // // ACSStringPool :: WriteStrings // -// Writes strings to a PNG chunk. +// Writes strings to a serializer // //============================================================================ -void ACSStringPool::WriteStrings(FILE *file, DWORD id) const +void ACSStringPool::WriteStrings(FSerializer &file, const char *key) const { int32_t i, poolsize = (int32_t)Pool.Size(); @@ -780,20 +779,29 @@ void ACSStringPool::WriteStrings(FILE *file, DWORD id) const { // No need to write if we don't have anything. return; } - FPNGChunkArchive arc(file, id); - - arc << poolsize; - for (i = 0; i < poolsize; ++i) + if (file.BeginObject(key)) { - PoolEntry *entry = &Pool[i]; - if (entry->Next != FREE_ENTRY) + file("poolsize", poolsize); + if (file.BeginArray("pool")) { - arc.WriteCount(i); - arc.WriteString(entry->Str); - arc.WriteCount(entry->LockCount); + for (i = 0; i < poolsize; ++i) + { + PoolEntry *entry = &Pool[i]; + if (entry->Next != FREE_ENTRY) + { + if (file.BeginObject(nullptr)) + { + file("index", i) + ("string", entry->Str) + ("lockcount", entry->LockCount) + .EndObject(); + } + } + } + file.EndArray(); } + file.EndObject(); } - arc.WriteCount(-1); } //============================================================================ @@ -943,7 +951,7 @@ void P_ClearACSVars(bool alsoglobal) // //============================================================================ -static void WriteVars (FILE *file, SDWORD *vars, size_t count, DWORD id) +static void WriteVars (FSerializer &file, SDWORD *vars, size_t count, const char *key) { size_t i, j; @@ -961,12 +969,7 @@ static void WriteVars (FILE *file, SDWORD *vars, size_t count, DWORD id) if (vars[j] != 0) break; } - FPNGChunkArchive arc (file, id); - for (i = 0; i <= j; ++i) - { - DWORD var = vars[i]; - arc << var; - } + file.Array(key, vars, int(j+1)); } } @@ -976,29 +979,10 @@ static void WriteVars (FILE *file, SDWORD *vars, size_t count, DWORD id) // //============================================================================ -static void ReadVars (PNGHandle *png, SDWORD *vars, size_t count, DWORD id) +static void ReadVars (FSerializer &arc, SDWORD *vars, size_t count, const char *key) { - size_t len = M_FindPNGChunk (png, id); - size_t used = 0; - - if (len != 0) - { - DWORD var; - size_t i; - FPNGChunkArchive arc (png->File->GetFile(), id, len); - used = len / 4; - - for (i = 0; i < used; ++i) - { - arc << var; - vars[i] = var; - } - png->File->ResetFilePtr(); - } - if (used < count) - { - memset (&vars[used], 0, (count-used)*4); - } + memset(&vars[0], 0, count * 4); + arc.Array(key, vars, (int)count); } //============================================================================ @@ -1007,9 +991,9 @@ static void ReadVars (PNGHandle *png, SDWORD *vars, size_t count, DWORD id) // //============================================================================ -static void WriteArrayVars (FILE *file, FWorldGlobalArray *vars, unsigned int count, DWORD id) +static void WriteArrayVars (FSerializer &file, FWorldGlobalArray *vars, unsigned int count, const char *key) { - unsigned int i, j; + unsigned int i; // Find the first non-empty array. for (i = 0; i < count; ++i) @@ -1019,29 +1003,67 @@ static void WriteArrayVars (FILE *file, FWorldGlobalArray *vars, unsigned int co } if (i < count) { - // Find last non-empty array. Anything beyond the last stored array - // will be emptied at load time. - for (j = count-1; j > i; --j) + if (file.BeginObject(key)) { - if (vars[j].CountUsed() != 0) - break; - } - FPNGChunkArchive arc (file, id); - arc.WriteCount (i); - arc.WriteCount (j); - for (; i <= j; ++i) - { - arc.WriteCount (vars[i].CountUsed()); - - FWorldGlobalArray::ConstIterator it(vars[i]); - const FWorldGlobalArray::Pair *pair; - - while (it.NextPair (pair)) + for(;iKey); - arc.WriteCount (pair->Value); + if (vars[i].CountUsed()) + { + FString arraykey; + + arraykey.Format("%d", i); + if (file.BeginObject(arraykey)) + { + FWorldGlobalArray::ConstIterator it(vars[i]); + const FWorldGlobalArray::Pair *pair; + + while (it.NextPair(pair)) + { + arraykey.Format("%d", pair->Key); + int v = pair->Value; + file(arraykey.GetChars(), v); + } + file.EndObject(); + } + } + } + file.EndObject(); + } + } +} + +//============================================================================ +// +// +// +//============================================================================ + +static void ReadArrayVars (FSerializer &file, FWorldGlobalArray *vars, size_t count, const char *key) +{ + for (size_t i = 0; i < count; ++i) + { + vars[i].Clear(); + } + + if (file.BeginObject(key)) + { + const char *arraykey; + while ((arraykey = file.GetKey())) + { + int i = (int)strtol(arraykey, nullptr, 10); + if (file.BeginObject(nullptr)) + { + while ((arraykey = file.GetKey())) + { + int k = (int)strtol(arraykey, nullptr, 10); + int val; + file(nullptr, val); + vars[i].Insert(k, val); + } + file.EndObject(); } } + file.EndObject(); } } @@ -1051,38 +1073,13 @@ static void WriteArrayVars (FILE *file, FWorldGlobalArray *vars, unsigned int co // //============================================================================ -static void ReadArrayVars (PNGHandle *png, FWorldGlobalArray *vars, size_t count, DWORD id) +void P_ReadACSVars(FSerializer &arc) { - size_t len = M_FindPNGChunk (png, id); - unsigned int i, k; - - for (i = 0; i < count; ++i) - { - vars[i].Clear (); - } - - if (len != 0) - { - DWORD max, size; - FPNGChunkArchive arc (png->File->GetFile(), id, len); - - i = arc.ReadCount (); - max = arc.ReadCount (); - - for (; i <= max; ++i) - { - size = arc.ReadCount (); - for (k = 0; k < size; ++k) - { - SDWORD key, val; - key = arc.ReadCount(); - - val = arc.ReadCount(); - vars[i].Insert (key, val); - } - } - png->File->ResetFilePtr(); - } + ReadVars (arc, ACS_WorldVars, NUM_WORLDVARS, "acsworldvars"); + ReadVars (arc, ACS_GlobalVars, NUM_GLOBALVARS, "acsglobalvars"); + ReadArrayVars (arc, ACS_WorldArrays, NUM_WORLDVARS, "acsworldarrays"); + ReadArrayVars (arc, ACS_GlobalArrays, NUM_GLOBALVARS, "acsglobalarrays"); + GlobalACSStrings.ReadStrings(arc, "acsglobalstrings"); } //============================================================================ @@ -1091,28 +1088,13 @@ static void ReadArrayVars (PNGHandle *png, FWorldGlobalArray *vars, size_t count // //============================================================================ -void P_ReadACSVars(PNGHandle *png) +void P_WriteACSVars(FSerializer &arc) { - ReadVars (png, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r')); - ReadVars (png, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); - ReadArrayVars (png, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); - ReadArrayVars (png, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); - GlobalACSStrings.ReadStrings(png, MAKE_ID('a','s','T','r')); -} - -//============================================================================ -// -// -// -//============================================================================ - -void P_WriteACSVars(FILE *stdfile) -{ - WriteVars (stdfile, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r')); - WriteVars (stdfile, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); - WriteArrayVars (stdfile, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); - WriteArrayVars (stdfile, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); - GlobalACSStrings.WriteStrings(stdfile, MAKE_ID('a','s','T','r')); + WriteVars (arc, ACS_WorldVars, NUM_WORLDVARS, "acsworldvars"); + WriteVars (arc, ACS_GlobalVars, NUM_GLOBALVARS, "acsglobalvars"); + WriteArrayVars (arc, ACS_WorldArrays, NUM_WORLDVARS, "acsworldarrays"); + WriteArrayVars (arc, ACS_GlobalArrays, NUM_GLOBALVARS, "acsglobalarrays"); + GlobalACSStrings.WriteStrings(arc, "acsglobalstrings"); } //---- Inventory functions --------------------------------------// @@ -1367,11 +1349,12 @@ public: int tag, int height, int special, int arg0, int arg1, int arg2, int arg3, int arg4); void Tick (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); private: sector_t *Sector; double WatchD, LastD; - int Special, Arg0, Arg1, Arg2, Arg3, Arg4; + int Special; + int Args[5]; TObjPtr Activator; line_t *Line; bool LineSide; @@ -1387,11 +1370,16 @@ END_POINTERS DPlaneWatcher::DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceiling, int tag, int height, int special, int arg0, int arg1, int arg2, int arg3, int arg4) - : Special (special), Arg0 (arg0), Arg1 (arg1), Arg2 (arg2), Arg3 (arg3), Arg4 (arg4), + : Special (special), Activator (it), Line (line), LineSide (!!lineSide), bCeiling (ceiling) { int secnum; + Args[0] = arg0; + Args[1] = arg1; + Args[2] = arg2; + Args[3] = arg3; + Args[4] = arg4; secnum = P_FindFirstSectorFromTag (tag); if (secnum >= 0) { @@ -1417,13 +1405,19 @@ DPlaneWatcher::DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceili } } -void DPlaneWatcher::Serialize (FArchive &arc) +void DPlaneWatcher::Serialize(FSerializer &arc) { Super::Serialize (arc); + arc("special", Special) + .Args("args", Args, nullptr, Special) + ("sector", Sector) + ("ceiling", bCeiling) + ("watchd", WatchD) + ("lastd", LastD) + ("activator", Activator) + ("line", Line) + ("lineside", LineSide); - arc << Special << Arg0 << Arg1 << Arg2 << Arg3 << Arg4 - << Sector << bCeiling << WatchD << LastD << Activator - << Line << LineSide << bCeiling; } void DPlaneWatcher::Tick () @@ -1448,7 +1442,7 @@ void DPlaneWatcher::Tick () if ((LastD < WatchD && newd >= WatchD) || (LastD > WatchD && newd <= WatchD)) { - P_ExecuteSpecial(Special, Line, Activator, LineSide, Arg0, Arg1, Arg2, Arg3, Arg4); + P_ExecuteSpecial(Special, Line, Activator, LineSide, Args[0], Args[1], Args[2], Args[3], Args[4]); Destroy (); } @@ -1614,131 +1608,117 @@ void FBehavior::UnlockMapVarStrings() const } } -void FBehavior::StaticSerializeModuleStates (FArchive &arc) +void FBehavior::StaticSerializeModuleStates (FSerializer &arc) { - DWORD modnum; + auto modnum = StaticModules.Size(); - modnum = StaticModules.Size(); - arc << modnum; - - if (modnum != StaticModules.Size()) + if (arc.BeginArray("acsmodules")) { - I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum); - } - - for (modnum = 0; modnum < StaticModules.Size(); ++modnum) - { - FBehavior *module = StaticModules[modnum]; - int ModSize = module->GetDataSize(); - - if (arc.IsStoring()) + if (arc.isReading()) { - arc.WriteString (module->ModuleName); - arc << ModSize; + int modnum = arc.ArraySize(); + if (modnum != StaticModules.Size()) + { + I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum); + } + } + + for (modnum = 0; modnum < StaticModules.Size(); ++modnum) + { + FBehavior *module = StaticModules[modnum]; + const char *modname = module->ModuleName; + int ModSize = module->GetDataSize(); + + if (arc.BeginObject(nullptr)) + { + arc.StringPtr("modname", modname) + ("modsize", ModSize); + + if (arc.isReading()) + { + if (stricmp(modname, module->ModuleName) != 0) + { + I_Error("Level was saved with a different set or order of ACS modules. (Have %s, save has %s)", module->ModuleName, modname); + } + else if (ModSize != module->GetDataSize()) + { + I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize); + } + } + module->SerializeVars(arc); + arc.EndObject(); + } + } + arc.EndArray(); + } +} + +void FBehavior::SerializeVars (FSerializer &arc) +{ + if (arc.BeginArray("variables")) + { + SerializeVarSet(arc, MapVarStore, NUM_MAPVARS); + for (int i = 0; i < NumArrays; ++i) + { + SerializeVarSet(arc, ArrayStore[i].Elements, ArrayStore[i].ArraySize); + } + arc.EndArray(); + } +} + +void FBehavior::SerializeVarSet (FSerializer &arc, SDWORD *vars, int max) +{ + SDWORD count; + SDWORD first, last; + + if (arc.BeginObject(nullptr)) + { + if (arc.isWriting()) + { + // Find first non-zero variable + for (first = 0; first < max; ++first) + { + if (vars[first] != 0) + { + break; + } + } + + // Find last non-zero variable + for (last = max - 1; last >= first; --last) + { + if (vars[last] != 0) + { + break; + } + } + + if (last < first) + { // no non-zero variables + count = 0; + arc("count", count); + } + else + { + count = last - first + 1; + arc("count", count); + arc("first", first); + arc.Array("values", &vars[first], count); + } } else { - char *modname = NULL; - arc << modname; - arc << ModSize; - if (stricmp (modname, module->ModuleName) != 0) - { - delete[] modname; - I_Error("Level was saved with a different set or order of ACS modules. (Have %s, save has %s)", module->ModuleName, modname); - } - else if (ModSize != module->GetDataSize()) - { - delete[] modname; - I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize); - } - delete[] modname; - } - module->SerializeVars (arc); - } -} + memset(vars, 0, max * sizeof(*vars)); + arc("count", count); -void FBehavior::SerializeVars (FArchive &arc) -{ - SerializeVarSet (arc, MapVarStore, NUM_MAPVARS); - for (int i = 0; i < NumArrays; ++i) - { - SerializeVarSet (arc, ArrayStore[i].Elements, ArrayStore[i].ArraySize); - } -} - -void FBehavior::SerializeVarSet (FArchive &arc, SDWORD *vars, int max) -{ - SDWORD arcval; - SDWORD first, last; - - if (arc.IsStoring ()) - { - // Find first non-zero variable - for (first = 0; first < max; ++first) - { - if (vars[first] != 0) + if (count != 0) { - break; + arc("first", first); + if (first + count > max) count = max - first; + arc.Array("values", &vars[first], count); } } - - // Find last non-zero variable - for (last = max - 1; last >= first; --last) - { - if (vars[last] != 0) - { - break; - } - } - - if (last < first) - { // no non-zero variables - arcval = 0; - arc << arcval; - return; - } - - arcval = last - first + 1; - arc << arcval; - arcval = first; - arc << arcval; - - while (first <= last) - { - arc << vars[first]; - ++first; - } - } - else - { - SDWORD truelast; - - memset (vars, 0, max*sizeof(*vars)); - - arc << last; - if (last == 0) - { - return; - } - arc << first; - last += first; - truelast = last; - - if (last > max) - { - last = max; - } - - while (first < last) - { - arc << vars[first]; - ++first; - } - while (first < truelast) - { - arc << arcval; - ++first; - } + arc.EndObject(); } } @@ -2869,33 +2849,6 @@ void FBehavior::StaticStopMyScripts (AActor *actor) } } -//========================================================================== -// -// P_SerializeACSScriptNumber -// -// Serializes a script number. If it's negative, it's really a name, so -// that will get serialized after it. -// -//========================================================================== - -void P_SerializeACSScriptNumber(FArchive &arc, int &scriptnum, bool was2byte) -{ - arc << scriptnum; - // If the script number is negative, then it's really a name. - // So read/store the name after it. - if (scriptnum < 0) - { - if (arc.IsStoring()) - { - arc.WriteName(FName(ENamedName(-scriptnum)).GetChars()); - } - else - { - const char *nam = arc.ReadName(); - scriptnum = -FName(nam); - } - } -} //---- The ACS Interpreter ----// @@ -2928,81 +2881,65 @@ DACSThinker::~DACSThinker () ActiveThinker = NULL; } -void DACSThinker::Serialize (FArchive &arc) +//========================================================================== +// +// helper class for the runningscripts serializer +// +//========================================================================== + +struct SavingRunningscript { int scriptnum; - int scriptcount = 0; + DLevelScript *lscript; +}; - Super::Serialize (arc); - if (arc.IsStoring()) +FSerializer &Serialize(FSerializer &arc, const char *key, SavingRunningscript &rs, SavingRunningscript *def) +{ + if (arc.BeginObject(key)) { - DLevelScript *script; - script = Scripts; - while (script) - { - scriptcount++; - - // We want to store this list backwards, so we can't loose the last pointer - if (script->next == NULL) - break; - script = script->next; - } - arc << scriptcount; - - while (script) - { - arc << script; - script = script->prev; - } + arc.ScriptNum("num", rs.scriptnum) + ("script", rs.lscript) + .EndObject(); } - else + return arc; +} + +void DACSThinker::Serialize(FSerializer &arc) +{ + Super::Serialize(arc); + arc("scripts", Scripts); + + if (arc.isWriting()) { - // We are running through this list backwards, so the next entry is the last processed - DLevelScript *next = NULL; - arc << scriptcount; - Scripts = NULL; - LastScript = NULL; - for (int i = 0; i < scriptcount; i++) + if (RunningScripts.CountUsed()) { - arc << Scripts; + ScriptMap::Iterator it(RunningScripts); + ScriptMap::Pair *pair; - Scripts->next = next; - Scripts->prev = NULL; - if (next != NULL) - next->prev = Scripts; - - next = Scripts; - - if (i == 0) - LastScript = Scripts; + arc.BeginArray("runningscripts"); + while (it.NextPair(pair)) + { + assert(pair->Value != nullptr); + SavingRunningscript srs = { pair->Key, pair->Value }; + arc(nullptr, srs); + } + arc.EndArray(); } } - if (arc.IsStoring ()) - { - ScriptMap::Iterator it(RunningScripts); - ScriptMap::Pair *pair; - - while (it.NextPair(pair)) - { - assert(pair->Value != NULL); - arc << pair->Value; - scriptnum = pair->Key; - P_SerializeACSScriptNumber(arc, scriptnum, true); - } - DLevelScript *nilptr = NULL; - arc << nilptr; - } else // Loading { - DLevelScript *script = NULL; + DLevelScript *script = nullptr; RunningScripts.Clear(); - - arc << script; - while (script) + if (arc.BeginArray("runningscripts")) { - P_SerializeACSScriptNumber(arc, scriptnum, true); - RunningScripts[scriptnum] = script; - arc << script; + auto cnt = arc.ArraySize(); + for (int i = 0; i < cnt; i++) + { + SavingRunningscript srs; + arc(nullptr, srs); + RunningScripts[srs.scriptnum] = srs.lscript; + } + arc.EndArray(); } } } @@ -3049,58 +2986,51 @@ IMPLEMENT_POINTY_CLASS (DLevelScript) DECLARE_POINTER(activator) END_POINTERS -inline FArchive &operator<< (FArchive &arc, DLevelScript::EScriptState &state) +//========================================================================== +// +// SerializeFFontPtr +// +//========================================================================== + +void DLevelScript::Serialize(FSerializer &arc) { - BYTE val = (BYTE)state; - arc << val; - state = (DLevelScript::EScriptState)val; - return arc; -} + Super::Serialize(arc); -void DLevelScript::Serialize (FArchive &arc) -{ - DWORD i; + uint32_t pcofs; + uint16_t lib; - Super::Serialize (arc); - - P_SerializeACSScriptNumber(arc, script, false); - - arc << state - << statedata - << activator - << activationline - << backSide - << numlocalvars; - - if (arc.IsLoading()) + if (arc.isWriting()) { - localvars = new SDWORD[numlocalvars]; - } - for (i = 0; i < (DWORD)numlocalvars; i++) - { - arc << localvars[i]; + lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT; + pcofs = activeBehavior->PC2Ofs(pc); } - if (arc.IsStoring ()) - { - WORD lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT; - arc << lib; - i = activeBehavior->PC2Ofs (pc); - arc << i; - } - else - { - WORD lib; - arc << lib << i; - activeBehavior = FBehavior::StaticGetModule (lib); - pc = activeBehavior->Ofs2PC (i); - } + arc.ScriptNum("scriptnum", script) + ("next", next) + ("prev", prev) + .Enum("state", state) + ("statedata", statedata) + ("activator", activator) + ("activationline", activationline) + ("backside", backSide) + ("localvars", Localvars) + ("lib", lib) + ("pc", pcofs) + ("activefont", activefont) + ("hudwidth", hudwidth) + ("hudheight", hudheight) + ("cliprectleft", ClipRectLeft) + ("cliprectop", ClipRectTop) + ("cliprectwidth", ClipRectWidth) + ("cliprectheight", ClipRectHeight) + ("wrapwidth", WrapWidth) + ("inmodulescriptnum", InModuleScriptNumber); - arc << activefont - << hudwidth << hudheight; - arc << ClipRectLeft << ClipRectTop << ClipRectWidth << ClipRectHeight - << WrapWidth; - arc << InModuleScriptNumber; + if (arc.isReading()) + { + activeBehavior = FBehavior::StaticGetModule(lib); + pc = activeBehavior->Ofs2PC(pcofs); + } } DLevelScript::DLevelScript () @@ -3109,14 +3039,10 @@ DLevelScript::DLevelScript () if (DACSThinker::ActiveThinker == NULL) new DACSThinker; activefont = SmallFont; - localvars = NULL; } DLevelScript::~DLevelScript () { - if (localvars != NULL) - delete[] localvars; - localvars = NULL; } void DLevelScript::Unlink () @@ -6141,7 +6067,7 @@ static bool CharArrayParms(int &capacity, int &offset, int &a, int *Stack, int & int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; - SDWORD *locals = localvars; + SDWORD *locals = &Localvars[0]; ACSLocalArrays noarrays; ACSLocalArrays *localarrays = &noarrays; ScriptFunction *activeFunction = NULL; @@ -9677,12 +9603,11 @@ DLevelScript::DLevelScript (AActor *who, line_t *where, int num, const ScriptPtr script = num; assert(code->VarCount >= code->ArgCount); - numlocalvars = code->VarCount; - localvars = new SDWORD[code->VarCount]; - memset(localvars, 0, code->VarCount * sizeof(SDWORD)); + Localvars.Resize(code->VarCount); + memset(&Localvars[0], 0, code->VarCount * sizeof(SDWORD)); for (int i = 0; i < MIN(argcount, code->ArgCount); ++i) { - localvars[i] = args[i]; + Localvars[i] = args[i]; } pc = module->GetScriptAddress(code); InModuleScriptNumber = module->GetScriptIndex(code); @@ -9727,15 +9652,13 @@ static void SetScriptState (int script, DLevelScript::EScriptState state) void P_DoDeferedScripts () { - acsdefered_t *def; const ScriptPtr *scriptdata; FBehavior *module; // Handle defered scripts in this step, too - def = level.info->defered; - while (def) + for(int i = level.info->deferred.Size()-1; i>=0; i--) { - acsdefered_t *next = def->next; + acsdefered_t *def = &level.info->deferred[i]; switch (def->type) { case acsdefered_t::defexecute: @@ -9766,39 +9689,35 @@ void P_DoDeferedScripts () DPrintf (DMSG_SPAMMY, "Deferred terminate of %s\n", ScriptPresentation(def->script).GetChars()); break; } - delete def; - def = next; } - level.info->defered = NULL; + level.info->deferred.Clear(); } static void addDefered (level_info_t *i, acsdefered_t::EType type, int script, const int *args, int argcount, AActor *who) { if (i) { - acsdefered_t *def = new acsdefered_t; + acsdefered_t &def = i->deferred[i->deferred.Reserve(1)]; int j; - def->next = i->defered; - def->type = type; - def->script = script; - for (j = 0; (size_t)j < countof(def->args) && j < argcount; ++j) + def.type = type; + def.script = script; + for (j = 0; (size_t)j < countof(def.args) && j < argcount; ++j) { - def->args[j] = args[j]; + def.args[j] = args[j]; } - while ((size_t)j < countof(def->args)) + while ((size_t)j < countof(def.args)) { - def->args[j++] = 0; + def.args[j++] = 0; } if (who != NULL && who->player != NULL) { - def->playernum = int(who->player - players); + def.playernum = int(who->player - players); } else { - def->playernum = -1; + def.playernum = -1; } - i->defered = def; DPrintf (DMSG_SPAMMY, "%s on map %s deferred\n", ScriptPresentation(script).GetChars(), i->MapName.GetChars()); } } @@ -9876,43 +9795,15 @@ void P_TerminateScript (int script, const char *map) SetScriptState (script, DLevelScript::SCRIPT_PleaseRemove); } -FArchive &operator<< (FArchive &arc, acsdefered_t *&defertop) +FSerializer &Serialize(FSerializer &arc, const char *key, acsdefered_t &defer, acsdefered_t *def) { - BYTE more; - - if (arc.IsStoring ()) + if (arc.BeginObject(key)) { - acsdefered_t *defer = defertop; - more = 1; - while (defer) - { - BYTE type; - arc << more; - type = (BYTE)defer->type; - arc << type; - P_SerializeACSScriptNumber(arc, defer->script, false); - arc << defer->playernum << defer->args[0] << defer->args[1] << defer->args[2]; - defer = defer->next; - } - more = 0; - arc << more; - } - else - { - acsdefered_t **defer = &defertop; - - arc << more; - while (more) - { - *defer = new acsdefered_t; - arc << more; - (*defer)->type = (acsdefered_t::EType)more; - P_SerializeACSScriptNumber(arc, (*defer)->script, false); - arc << (*defer)->playernum << (*defer)->args[0] << (*defer)->args[1] << (*defer)->args[2]; - defer = &((*defer)->next); - arc << more; - } - *defer = NULL; + arc.Enum("type", defer.type) + .ScriptNum("script", defer.script) + .Array("args", defer.args, 3) + ("player", defer.playernum) + .EndObject(); } return arc; } diff --git a/src/p_acs.h b/src/p_acs.h index d26829c616..a0136ae183 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -44,6 +44,7 @@ class FFont; class FileReader; +struct line_t; enum @@ -94,8 +95,8 @@ public: void PurgeStrings(); void Clear(); void Dump() const; - void ReadStrings(PNGHandle *png, DWORD id); - void WriteStrings(FILE *file, DWORD id) const; + void ReadStrings(FSerializer &file, const char *key); + void WriteStrings(FSerializer &file, const char *key) const; private: int FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum); @@ -120,10 +121,9 @@ private: extern ACSStringPool GlobalACSStrings; void P_CollectACSGlobalStrings(); -void P_ReadACSVars(PNGHandle *); -void P_WriteACSVars(FILE*); +void P_ReadACSVars(FSerializer &); +void P_WriteACSVars(FSerializer &); void P_ClearACSVars(bool); -void P_SerializeACSScriptNumber(FArchive &arc, int &scriptnum, bool was2byte); struct ACSProfileInfo { @@ -325,7 +325,7 @@ public: static void StaticUnloadModules (); static bool StaticCheckAllGood (); static FBehavior *StaticGetModule (int lib); - static void StaticSerializeModuleStates (FArchive &arc); + static void StaticSerializeModuleStates (FSerializer &arc); static void StaticMarkLevelVarStrings(); static void StaticLockLevelVarStrings(); static void StaticUnlockLevelVarStrings(); @@ -369,8 +369,8 @@ private: void UnescapeStringTable(BYTE *chunkstart, BYTE *datastart, bool haspadding); int FindStringInChunk (DWORD *chunk, const char *varname) const; - void SerializeVars (FArchive &arc); - void SerializeVarSet (FArchive &arc, SDWORD *vars, int max); + void SerializeVars (FSerializer &arc); + void SerializeVarSet (FSerializer &arc, SDWORD *vars, int max); void MarkMapVarStrings() const; void LockMapVarStrings() const; @@ -860,7 +860,7 @@ public: const int *args, int argcount, int flags); ~DLevelScript (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); int RunScript (); inline void SetState (EScriptState newstate) { state = newstate; } @@ -870,22 +870,21 @@ public: void MarkLocalVarStrings() const { - GlobalACSStrings.MarkStringArray(localvars, numlocalvars); + GlobalACSStrings.MarkStringArray(&Localvars[0], Localvars.Size()); } void LockLocalVarStrings() const { - GlobalACSStrings.LockStringArray(localvars, numlocalvars); + GlobalACSStrings.LockStringArray(&Localvars[0], Localvars.Size()); } void UnlockLocalVarStrings() const { - GlobalACSStrings.UnlockStringArray(localvars, numlocalvars); + GlobalACSStrings.UnlockStringArray(&Localvars[0], Localvars.Size()); } protected: DLevelScript *next, *prev; int script; - SDWORD *localvars; - int numlocalvars; + TArray Localvars; int *pc; EScriptState state; int statedata; @@ -945,7 +944,7 @@ public: DACSThinker (); ~DACSThinker (); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); typedef TMap ScriptMap; @@ -966,8 +965,6 @@ private: // The structure used to control scripts between maps struct acsdefered_t { - struct acsdefered_t *next; - enum EType { defexecute, @@ -980,6 +977,6 @@ struct acsdefered_t int playernum; }; -FArchive &operator<< (FArchive &arc, acsdefered_t *&defer); +FSerializer &Serialize(FSerializer &arc, const char *key, acsdefered_t &defer, acsdefered_t *def); #endif //__P_ACS_H__ diff --git a/src/p_ceiling.cpp b/src/p_ceiling.cpp index 888b67b6c9..32e5d62bed 100644 --- a/src/p_ceiling.cpp +++ b/src/p_ceiling.cpp @@ -29,31 +29,9 @@ #include "doomstat.h" #include "r_state.h" #include "gi.h" -#include "farchive.h" +#include "serializer.h" #include "p_spec.h" -//============================================================================ -// -// -// -//============================================================================ - -inline FArchive &operator<< (FArchive &arc, DCeiling::ECeiling &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DCeiling::ECeiling)val; - return arc; -} - -inline FArchive &operator<< (FArchive &arc, DCeiling::ECrushMode &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DCeiling::ECrushMode)val; - return arc; -} - //============================================================================ // // CEILINGS @@ -72,23 +50,23 @@ DCeiling::DCeiling () // //============================================================================ -void DCeiling::Serialize (FArchive &arc) +void DCeiling::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_BottomHeight - << m_TopHeight - << m_Speed - << m_Speed1 - << m_Speed2 - << m_Crush - << m_Silent - << m_Direction - << m_Texture - << m_NewSpecial - << m_Tag - << m_OldDirection - << m_CrushMode; + arc.Enum("type", m_Type) + ("bottomheight", m_BottomHeight) + ("topheight", m_TopHeight) + ("speed", m_Speed) + ("speed1", m_Speed1) + ("speed2", m_Speed2) + ("crush", m_Crush) + ("silent", m_Silent) + ("direction", m_Direction) + ("texture", m_Texture) + ("newspecial", m_NewSpecial) + ("tag", m_Tag) + ("olddirecton", m_OldDirection) + .Enum("crushmode", m_CrushMode); } //============================================================================ diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 5456033688..a697223e75 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -57,7 +57,6 @@ #include "doomstat.h" #include "c_console.h" #include "sbar.h" -#include "farchive.h" #include "p_lnspec.h" #include "r_utility.h" #include "p_local.h" @@ -1470,25 +1469,3 @@ static void TerminalResponse (const char *str) } } - -template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node) -{ - DWORD convnum; - if (arc.IsStoring()) - { - arc.WriteCount (node == NULL? ~0u : node->ThisNodeNum); - } - else - { - convnum = arc.ReadCount(); - if (convnum >= StrifeDialogues.Size()) - { - node = NULL; - } - else - { - node = StrifeDialogues[convnum]; - } - } - return arc; -} diff --git a/src/p_doors.cpp b/src/p_doors.cpp index ab551de1b3..33e37218d7 100644 --- a/src/p_doors.cpp +++ b/src/p_doors.cpp @@ -35,7 +35,7 @@ #include "i_system.h" #include "sc_man.h" #include "cmdlib.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" #include "p_spec.h" @@ -47,29 +47,23 @@ IMPLEMENT_CLASS (DDoor) -inline FArchive &operator<< (FArchive &arc, DDoor::EVlDoor &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DDoor::EVlDoor)val; - return arc; -} - DDoor::DDoor () { } -void DDoor::Serialize (FArchive &arc) +void DDoor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_TopDist - << m_BotSpot << m_BotDist << m_OldFloorDist - << m_Speed - << m_Direction - << m_TopWait - << m_TopCountdown - << m_LightTag; + arc.Enum("type", m_Type) + ("topdist", m_TopDist) + ("botspot", m_BotSpot) + ("botdist", m_BotDist) + ("oldfloordist", m_OldFloorDist) + ("speed", m_Speed) + ("direction", m_Direction) + ("topwait", m_TopWait) + ("topcountdown", m_TopCountdown) + ("lighttag", m_LightTag); } //============================================================================ @@ -530,19 +524,21 @@ DAnimatedDoor::DAnimatedDoor (sector_t *sec) { } -void DAnimatedDoor::Serialize (FArchive &arc) +void DAnimatedDoor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Line1 << m_Line2 - << m_Frame - << m_Timer - << m_BotDist - << m_Status - << m_Speed - << m_Delay - << m_DoorAnim - << m_SetBlocking1 << m_SetBlocking2; + arc("line1", m_Line1) + ("line2", m_Line2) + ("frame", m_Frame) + ("timer", m_Timer) + ("botdist", m_BotDist) + ("status", m_Status) + ("speed", m_Speed) + ("delay", m_Delay) + ("dooranim", m_DoorAnim) + ("setblock1", m_SetBlocking1) + ("setblock2", m_SetBlocking2); } //============================================================================ diff --git a/src/p_floor.cpp b/src/p_floor.cpp index ccb1c82e97..8d09ea2d52 100644 --- a/src/p_floor.cpp +++ b/src/p_floor.cpp @@ -28,7 +28,7 @@ #include "s_sndseq.h" #include "doomstat.h" #include "r_state.h" -#include "farchive.h" +#include "serializer.h" #include "p_3dmidtex.h" #include "p_spec.h" #include "r_data/r_interpolate.h" @@ -39,20 +39,6 @@ // //========================================================================== -inline FArchive &operator<< (FArchive &arc, DFloor::EFloor &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DFloor::EFloor)val; - return arc; -} - -//========================================================================== -// -// -// -//========================================================================== - static void StartFloorSound (sector_t *sec) { if (sec->Flags & SECF_SILENTMOVE) return; @@ -84,23 +70,23 @@ DFloor::DFloor () { } -void DFloor::Serialize (FArchive &arc) +void DFloor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_Crush - << m_Direction - << m_NewSpecial - << m_Texture - << m_FloorDestDist - << m_Speed - << m_ResetCount - << m_OrgDist - << m_Delay - << m_PauseTime - << m_StepTime - << m_PerStepTime - << m_Hexencrush; + arc.Enum("type", m_Type) + ("crush", m_Crush) + ("direction", m_Direction) + ("newspecial", m_NewSpecial) + ("texture", m_Texture) + ("floordestdist", m_FloorDestDist) + ("speed", m_Speed) + ("resetcount", m_ResetCount) + ("orgdist", m_OrgDist) + ("delay", m_Delay) + ("pausetime", m_PauseTime) + ("steptime", m_StepTime) + ("persteptime", m_PerStepTime) + ("crushmode", m_Hexencrush); } //========================================================================== @@ -834,14 +820,6 @@ IMPLEMENT_POINTY_CLASS (DElevator) DECLARE_POINTER(m_Interp_Ceiling) END_POINTERS -inline FArchive &operator<< (FArchive &arc, DElevator::EElevator &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DElevator::EElevator)val; - return arc; -} - DElevator::DElevator () { } @@ -855,16 +833,16 @@ DElevator::DElevator (sector_t *sec) m_Interp_Ceiling = sec->SetInterpolation(sector_t::CeilingMove, true); } -void DElevator::Serialize (FArchive &arc) +void DElevator::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_Direction - << m_FloorDestDist - << m_CeilingDestDist - << m_Speed - << m_Interp_Floor - << m_Interp_Ceiling; + arc.Enum("type", m_Type) + ("direction", m_Direction) + ("floordestdist", m_FloorDestDist) + ("ceilingdestdist", m_CeilingDestDist) + ("speed", m_Speed) + ("interp_floor", m_Interp_Floor) + ("interp_ceiling", m_Interp_Ceiling); } //========================================================================== @@ -1125,9 +1103,7 @@ bool EV_DoChange (line_t *line, EChange changetype, int tag) // //========================================================================== -IMPLEMENT_POINTY_CLASS (DWaggleBase) - DECLARE_POINTER(m_Interpolation) -END_POINTERS +IMPLEMENT_CLASS (DWaggleBase) IMPLEMENT_CLASS (DFloorWaggle) IMPLEMENT_CLASS (DCeilingWaggle) @@ -1136,18 +1112,17 @@ DWaggleBase::DWaggleBase () { } -void DWaggleBase::Serialize (FArchive &arc) +void DWaggleBase::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_OriginalDist - << m_Accumulator - << m_AccDelta - << m_TargetScale - << m_Scale - << m_ScaleDelta - << m_Ticker - << m_State - << m_Interpolation; + arc("originaldist", m_OriginalDist) + ("accumulator", m_Accumulator) + ("accdelta", m_AccDelta) + ("targetscale", m_TargetScale) + ("scale", m_Scale) + ("scaledelta", m_ScaleDelta) + ("ticker", m_Ticker) + ("state", m_State); } //========================================================================== @@ -1167,11 +1142,6 @@ DWaggleBase::DWaggleBase (sector_t *sec) void DWaggleBase::Destroy() { - if (m_Interpolation != NULL) - { - m_Interpolation->DelRef(); - m_Interpolation = NULL; - } Super::Destroy(); } @@ -1266,7 +1236,7 @@ DFloorWaggle::DFloorWaggle (sector_t *sec) : Super (sec) { sec->floordata = this; - m_Interpolation = sec->SetInterpolation(sector_t::FloorMove, true); + interpolation = sec->SetInterpolation(sector_t::FloorMove, true); } void DFloorWaggle::Tick () @@ -1288,7 +1258,7 @@ DCeilingWaggle::DCeilingWaggle (sector_t *sec) : Super (sec) { sec->ceilingdata = this; - m_Interpolation = sec->SetInterpolation(sector_t::CeilingMove, true); + interpolation = sec->SetInterpolation(sector_t::CeilingMove, true); } void DCeilingWaggle::Tick () diff --git a/src/p_lights.cpp b/src/p_lights.cpp index 90d1da869a..fb3ab339e4 100644 --- a/src/p_lights.cpp +++ b/src/p_lights.cpp @@ -35,7 +35,7 @@ // State. #include "r_state.h" #include "statnums.h" -#include "farchive.h" +#include "serializer.h" static FRandom pr_flicker ("Flicker"); static FRandom pr_lightflash ("LightFlash"); @@ -49,7 +49,7 @@ class DFireFlicker : public DLighting public: DFireFlicker(sector_t *sector); DFireFlicker(sector_t *sector, int upper, int lower); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_Count; @@ -64,7 +64,7 @@ class DFlicker : public DLighting DECLARE_CLASS(DFlicker, DLighting) public: DFlicker(sector_t *sector, int upper, int lower); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_Count; @@ -80,7 +80,7 @@ class DLightFlash : public DLighting public: DLightFlash(sector_t *sector); DLightFlash(sector_t *sector, int min, int max); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_Count; @@ -98,7 +98,7 @@ class DStrobe : public DLighting public: DStrobe(sector_t *sector, int utics, int ltics, bool inSync); DStrobe(sector_t *sector, int upper, int lower, int utics, int ltics); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_Count; @@ -115,7 +115,7 @@ class DGlow : public DLighting DECLARE_CLASS(DGlow, DLighting) public: DGlow(sector_t *sector); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_MinLight; @@ -131,7 +131,7 @@ class DGlow2 : public DLighting DECLARE_CLASS(DGlow2, DLighting) public: DGlow2(sector_t *sector, int start, int end, int tics, bool oneshot); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: int m_Start; @@ -150,7 +150,7 @@ class DPhased : public DLighting public: DPhased(sector_t *sector); DPhased(sector_t *sector, int baselevel, int phase); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: BYTE m_BaseLevel; @@ -197,10 +197,12 @@ DFireFlicker::DFireFlicker () { } -void DFireFlicker::Serialize (FArchive &arc) +void DFireFlicker::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Count << m_MaxLight << m_MinLight; + arc("count", m_Count) + ("maxlight", m_MaxLight) + ("minlight", m_MinLight); } @@ -262,10 +264,12 @@ DFlicker::DFlicker () { } -void DFlicker::Serialize (FArchive &arc) +void DFlicker::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Count << m_MaxLight << m_MinLight; + arc("count", m_Count) + ("maxlight", m_MaxLight) + ("minlight", m_MinLight); } //----------------------------------------------------------------------------- @@ -336,10 +340,14 @@ DLightFlash::DLightFlash () { } -void DLightFlash::Serialize (FArchive &arc) +void DLightFlash::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Count << m_MaxLight << m_MaxTime << m_MinLight << m_MinTime; + arc("count", m_Count) + ("maxlight", m_MaxLight) + ("minlight", m_MinLight) + ("maxtime", m_MaxTime) + ("mintime", m_MinTime); } //----------------------------------------------------------------------------- @@ -407,10 +415,14 @@ DStrobe::DStrobe () { } -void DStrobe::Serialize (FArchive &arc) +void DStrobe::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Count << m_MaxLight << m_MinLight << m_DarkTime << m_BrightTime; + arc("count", m_Count) + ("maxlight", m_MaxLight) + ("minlight", m_MinLight) + ("darktime", m_DarkTime) + ("brighttime", m_BrightTime); } //----------------------------------------------------------------------------- @@ -661,10 +673,12 @@ DGlow::DGlow () { } -void DGlow::Serialize (FArchive &arc) +void DGlow::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Direction << m_MaxLight << m_MinLight; + arc("direction", m_Direction) + ("maxlight", m_MaxLight) + ("minlight", m_MinLight); } //----------------------------------------------------------------------------- @@ -728,10 +742,14 @@ DGlow2::DGlow2 () { } -void DGlow2::Serialize (FArchive &arc) +void DGlow2::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_End << m_MaxTics << m_OneShot << m_Start << m_Tics; + arc("end", m_End) + ("maxtics", m_MaxTics) + ("oneshot", m_OneShot) + ("start", m_Start) + ("tics", m_Tics); } //----------------------------------------------------------------------------- @@ -857,10 +875,11 @@ DPhased::DPhased () { } -void DPhased::Serialize (FArchive &arc) +void DPhased::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_BaseLevel << m_Phase; + arc("baselevel", m_BaseLevel) + ("phase", m_Phase); } //----------------------------------------------------------------------------- diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index d38901f1e1..657d5db49c 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -64,12 +64,12 @@ #include "v_palette.h" #include "p_enemy.h" #include "gstrings.h" -#include "farchive.h" #include "r_data/colormaps.h" #include "r_renderer.h" #include "po_man.h" #include "p_spec.h" #include "p_checkposition.h" +#include "serializer.h" // MACROS ------------------------------------------------------------------ @@ -155,214 +155,194 @@ AActor::~AActor () // //========================================================================== -void AActor::Serialize(FArchive &arc) +#define A(a,b) ((a), (b), def->b) + +void AActor::Serialize(FSerializer &arc) { + AActor *def = GetDefault(); + Super::Serialize(arc); - if (arc.IsStoring()) - { - arc.WriteSprite(sprite); - } - else - { - sprite = arc.ReadSprite(); - } - - arc << __Pos - << Angles.Yaw - << Angles.Pitch - << Angles.Roll - << frame - << Scale - << RenderStyle - << renderflags - << picnum - << floorpic - << ceilingpic - << TIDtoHate - << LastLookPlayerNumber - << LastLookActor - << effects - << Alpha - << fillcolor - << Sector - << floorz - << ceilingz - << dropoffz - << floorsector - << ceilingsector - << radius - << Height - << projectilepassheight - << Vel - << tics - << state - << DamageVal; - if (DamageVal == 0x40000000 || DamageVal == -1) - { - DamageVal = -1; - DamageFunc = GetDefault()->DamageFunc; - } - else - { - DamageFunc = nullptr; - } - P_SerializeTerrain(arc, floorterrain); - arc << projectileKickback - << flags - << flags2 - << flags3 - << flags4 - << flags5 - << flags6 - << flags7 - << weaponspecial - << special1 - << special2 - << specialf1 - << specialf2 - << health - << movedir - << visdir - << movecount - << strafecount - << target - << lastenemy - << LastHeard - << reactiontime - << threshold - << player - << SpawnPoint - << SpawnAngle - << StartHealth - << skillrespawncount - << tracer - << Floorclip - << tid - << special; - if (P_IsACSSpecial(special)) - { - P_SerializeACSScriptNumber(arc, args[0], false); - } - else - { - arc << args[0]; - } - arc << args[1] << args[2] << args[3] << args[4]; - arc << accuracy << stamina; - arc << goal - << waterlevel - << MinMissileChance - << SpawnFlags - << Inventory - << InventoryID; - arc << FloatBobPhase - << Translation - << SeeSound - << AttackSound - << PainSound - << DeathSound - << ActiveSound - << UseSound - << BounceSound - << WallBounceSound - << CrushPainSound - << Speed - << FloatSpeed - << Mass - << PainChance - << SpawnState - << SeeState - << MeleeState - << MissileState - << MaxDropOffHeight - << MaxStepHeight - << BounceFlags - << bouncefactor - << wallbouncefactor - << bouncecount - << maxtargetrange - << meleethreshold - << meleerange - << DamageType; - arc << DamageTypeReceived; - arc << PainType - << DeathType; - arc << Gravity - << FastChaseStrafeCount - << master - << smokecounter - << BlockingMobj - << BlockingLine - << VisibleToTeam // [BB] - << pushfactor - << Species - << Score; - arc << DesignatedTeam; - arc << lastpush << lastbump - << PainThreshold - << DamageFactor; - arc << DamageMultiply; - arc << WeaveIndexXY << WeaveIndexZ - << PoisonDamageReceived << PoisonDurationReceived << PoisonPeriodReceived << Poisoner - << PoisonDamage << PoisonDuration << PoisonPeriod; - arc << PoisonDamageType << PoisonDamageTypeReceived; - arc << ConversationRoot << Conversation; - arc << FriendPlayer; - arc << TeleFogSourceType - << TeleFogDestType; - arc << RipperLevel - << RipLevelMin - << RipLevelMax; - arc << DefThreshold; - if (SaveVersion >= 4549) - { - arc << SpriteAngle; - arc << SpriteRotation; - } - - if (SaveVersion >= 4550) - { - arc << alternative; - } - - { - FString tagstr; - if (arc.IsStoring() && Tag != NULL && Tag->Len() > 0) tagstr = *Tag; - arc << tagstr; - if (arc.IsLoading()) - { - if (tagstr.Len() == 0) Tag = NULL; - else Tag = mStringPropertyData.Alloc(tagstr); - } - } - - if (arc.IsLoading ()) - { - touching_sectorlist = NULL; - LinkToWorld(false, Sector); - - AddToHash (); - SetShade (fillcolor); - if (player) - { - if (playeringame[player - players] && - player->cls != NULL && - !(flags4 & MF4_NOSKIN) && - state->sprite == GetDefaultByType (player->cls)->SpawnState->sprite) - { // Give player back the skin - sprite = skins[player->userinfo.GetSkin()].sprite; - } - if (Speed == 0) - { - Speed = GetDefault()->Speed; - } - } - ClearInterpolation(); - UpdateWaterLevel(false); - } + arc + .Sprite("sprite", sprite, &def->sprite) + A("pos", __Pos) + A("angles", Angles) + A("frame", frame) + A("scale", Scale) + A("renderstyle", RenderStyle) + A("renderflags", renderflags) + A("picnum", picnum) + A("floorpic", floorpic) + A("ceilingpic", ceilingpic) + A("tidtohate", TIDtoHate) + A("lastlookpn", LastLookPlayerNumber) + ("lastlookactor", LastLookActor) + A("effects", effects) + A("alpha", Alpha) + A("fillcolor", fillcolor) + A("sector", Sector) + A("floorz", floorz) + A("ceilingz", ceilingz) + A("dropoffz", dropoffz) + A("floorsector", floorsector) + A("ceilingsector", ceilingsector) + A("radius", radius) + A("height", Height) + A("ppassheight", projectilepassheight) + A("vel", Vel) + A("tics", tics) + A("state", state) + A("damage", DamageVal) + .Terrain("floorterrain", floorterrain, &def->floorterrain) + A("projectilekickback", projectileKickback) + A("flags", flags) + A("flags2", flags2) + A("flags3", flags3) + A("flags4", flags4) + A("flags5", flags5) + A("flags6", flags6) + A("flags7", flags7) + A("weaponspecial", weaponspecial) + A("special1", special1) + A("special2", special2) + A("specialf1", specialf1) + A("specialf2", specialf2) + A("health", health) + A("movedir", movedir) + A("visdir", visdir) + A("movecount", movecount) + A("strafecount", strafecount) + ("target", target) + ("lastenemy", lastenemy) + ("lastheard", LastHeard) + A("reactiontime", reactiontime) + A("threshold", threshold) + A("player", player) + A("spawnpoint", SpawnPoint) + A("spawnangle", SpawnAngle) + A("starthealth", StartHealth) + A("skillrespawncount", skillrespawncount) + ("tracer", tracer) + A("floorclip", Floorclip) + A("tid", tid) + A("special", special) + .Args("args", args, def->args, special) + A("accuracy", accuracy) + A("stamina", stamina) + ("goal", goal) + A("waterlevel", waterlevel) + A("minmissilechance", MinMissileChance) + A("spawnflags", SpawnFlags) + ("inventory", Inventory) + A("inventoryid", InventoryID) + A("floatbobphase", FloatBobPhase) + A("translation", Translation) + A("seesound", SeeSound) + A("attacksound", AttackSound) + A("paimsound", PainSound) + A("deathsound", DeathSound) + A("activesound", ActiveSound) + A("usesound", UseSound) + A("bouncesound", BounceSound) + A("wallbouncesound", WallBounceSound) + A("crushpainsound", CrushPainSound) + A("speed", Speed) + A("floatspeed", FloatSpeed) + A("mass", Mass) + A("painchance", PainChance) + A("spawnstate", SpawnState) + A("seestate", SeeState) + A("meleestate", MeleeState) + A("missilestate", MissileState) + A("maxdropoffheight", MaxDropOffHeight) + A("maxstepheight", MaxStepHeight) + A("bounceflags", BounceFlags) + A("bouncefactor", bouncefactor) + A("wallbouncefactor", wallbouncefactor) + A("bouncecount", bouncecount) + A("maxtargetrange", maxtargetrange) + A("meleethreshold", meleethreshold) + A("meleerange", meleerange) + A("damagetype", DamageType) + A("damagetypereceived", DamageTypeReceived) + A("paintype", PainType) + A("deathtype", DeathType) + A("gravity", Gravity) + A("fastchasestrafecount", FastChaseStrafeCount) + ("master", master) + A("smokecounter", smokecounter) + ("blockingmobj", BlockingMobj) + A("blockingline", BlockingLine) + A("visibletoteam", VisibleToTeam) + A("pushfactor", pushfactor) + A("species", Species) + A("score", Score) + A("designatedteam", DesignatedTeam) + A("lastpush", lastpush) + A("lastbump", lastbump) + A("painthreshold", PainThreshold) + A("damagefactor", DamageFactor) + A("damagemultiply", DamageMultiply) + A("waveindexxy", WeaveIndexXY) + A("weaveindexz", WeaveIndexZ) + A("pdmgreceived", PoisonDamageReceived) + A("pdurreceived", PoisonDurationReceived) + A("ppreceived", PoisonPeriodReceived) + ("poisoner", Poisoner) + A("posiondamage", PoisonDamage) + A("poisonduration", PoisonDuration) + A("poisonperiod", PoisonPeriod) + A("poisondamagetype", PoisonDamageType) + A("poisondmgtypereceived", PoisonDamageTypeReceived) + A("conversationroot", ConversationRoot) + A("conversation", Conversation) + A("friendplayer", FriendPlayer) + A("telefogsourcetype", TeleFogSourceType) + A("telefogdesttype", TeleFogDestType) + A("ripperlevel", RipperLevel) + A("riplevelmin", RipLevelMin) + A("riplevelmax", RipLevelMax) + A("devthreshold", DefThreshold) + A("spriteangle", SpriteAngle) + A("spriterotation", SpriteRotation) + ("alternative", alternative) + A("tag", Tag); } +#undef A + +//========================================================================== +// +// This must be done after the world is set up. +// +//========================================================================== + +void AActor::PostSerialize() +{ + touching_sectorlist = NULL; + LinkToWorld(false, Sector); + + AddToHash(); + if (player) + { + if (playeringame[player - players] && + player->cls != NULL && + !(flags4 & MF4_NOSKIN) && + state->sprite == GetDefaultByType(player->cls)->SpawnState->sprite) + { // Give player back the skin + sprite = skins[player->userinfo.GetSkin()].sprite; + } + if (Speed == 0) + { + Speed = GetDefault()->Speed; + } + } + ClearInterpolation(); + UpdateWaterLevel(false); +} + + + AActor::AActor () throw() { } @@ -3450,7 +3430,7 @@ void AActor::Tick () // [RH] Consider carrying sectors here DVector2 cumm(0, 0); - if ((level.Scrolls != NULL || player != NULL) && !(flags & MF_NOCLIP) && !(flags & MF_NOSECTOR)) + if ((level.Scrolls.Size() != 0 || player != NULL) && !(flags & MF_NOCLIP) && !(flags & MF_NOSECTOR)) { double height, waterheight; // killough 4/4/98: add waterheight const msecnode_t *node; @@ -3471,10 +3451,9 @@ void AActor::Tick () sector_t *sec = node->m_sector; DVector2 scrollv; - if (level.Scrolls != NULL) + if (level.Scrolls.Size() > (sec-sectors)) { - const FSectorScrollValues *scroll = &level.Scrolls[sec - sectors]; - scrollv = scroll->Scroll; + scrollv = level.Scrolls[sec - sectors]; } else { @@ -4052,7 +4031,6 @@ AActor *AActor::StaticSpawn (PClassActor *type, const DVector3 &pos, replace_t a actor->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98 if (G_SkillProperty(SKILLP_FastMonsters) && actor->GetClass()->FastSpeed >= 0) actor->Speed = actor->GetClass()->FastSpeed; - actor->DamageMultiply = 1.; // set subsector and/or block links actor->LinkToWorld (SpawningMapThing); diff --git a/src/p_pillar.cpp b/src/p_pillar.cpp index 2a134cc50e..a55bd5cdb8 100644 --- a/src/p_pillar.cpp +++ b/src/p_pillar.cpp @@ -37,7 +37,7 @@ #include "p_spec.h" #include "g_level.h" #include "s_sndseq.h" -#include "farchive.h" +#include "serializer.h" #include "r_data/r_interpolate.h" IMPLEMENT_POINTY_CLASS (DPillar) @@ -45,14 +45,6 @@ IMPLEMENT_POINTY_CLASS (DPillar) DECLARE_POINTER(m_Interp_Ceiling) END_POINTERS -inline FArchive &operator<< (FArchive &arc, DPillar::EPillar &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DPillar::EPillar)val; - return arc; -} - DPillar::DPillar () { } @@ -72,18 +64,18 @@ void DPillar::Destroy() Super::Destroy(); } -void DPillar::Serialize (FArchive &arc) +void DPillar::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_FloorSpeed - << m_CeilingSpeed - << m_FloorTarget - << m_CeilingTarget - << m_Crush - << m_Hexencrush - << m_Interp_Floor - << m_Interp_Ceiling; + arc.Enum("type", m_Type) + ("floorspeed", m_FloorSpeed) + ("ceilingspeed", m_CeilingSpeed) + ("floortarget", m_FloorTarget) + ("ceilingtarget", m_CeilingTarget) + ("crush", m_Crush) + ("hexencrush", m_Hexencrush) + ("interp_floor", m_Interp_Floor) + ("interp_ceiling", m_Interp_Ceiling); } void DPillar::Tick () diff --git a/src/p_plats.cpp b/src/p_plats.cpp index 96fec71c11..868f471383 100644 --- a/src/p_plats.cpp +++ b/src/p_plats.cpp @@ -30,45 +30,30 @@ #include "doomstat.h" #include "r_state.h" #include "gi.h" -#include "farchive.h" +#include "serializer.h" #include "p_spec.h" static FRandom pr_doplat ("DoPlat"); IMPLEMENT_CLASS (DPlat) -inline FArchive &operator<< (FArchive &arc, DPlat::EPlatType &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DPlat::EPlatType)val; - return arc; -} -inline FArchive &operator<< (FArchive &arc, DPlat::EPlatState &state) -{ - BYTE val = (BYTE)state; - arc << val; - state = (DPlat::EPlatState)val; - return arc; -} - DPlat::DPlat () { } -void DPlat::Serialize (FArchive &arc) +void DPlat::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Speed - << m_Low - << m_High - << m_Wait - << m_Count - << m_Status - << m_OldStatus - << m_Crush - << m_Tag - << m_Type; + arc.Enum("type", m_Type) + ("speed", m_Speed) + ("low", m_Low) + ("high", m_High) + ("wait", m_Wait) + ("count", m_Count) + .Enum("status", m_Status) + .Enum("oldstatus", m_OldStatus) + ("crush", m_Crush) + ("tag", m_Tag); } void DPlat::PlayPlatSound (const char *sound) diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index 099a39f020..67a447f61f 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -27,8 +27,8 @@ #include "templates.h" #include "thingdef/thingdef.h" #include "g_level.h" -#include "farchive.h" #include "d_player.h" +#include "serializer.h" // MACROS ------------------------------------------------------------------ @@ -1448,13 +1448,23 @@ void DPSprite::Tick() // //------------------------------------------------------------------------ -void DPSprite::Serialize(FArchive &arc) +void DPSprite::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << Next << Caller << Owner << Flags - << State << Tics << Sprite << Frame - << ID << x << y << oldx << oldy; + arc("next", Next) + ("caller", Caller) + ("owner", Owner) + ("flags", Flags) + ("state", State) + ("tics", Tics) + .Sprite("sprite", Sprite, nullptr) + ("frame", Frame) + ("id", ID) + ("x", x) + ("y", y) + ("oldx", oldx) + ("oldy", oldy); } //------------------------------------------------------------------------ diff --git a/src/p_pspr.h b/src/p_pspr.h index f9d64aef10..c0a695e041 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -35,7 +35,6 @@ #define WEAPONTOP (32+6./16) class AInventory; -class FArchive; // // Overlay psprites are scaled shapes @@ -88,7 +87,7 @@ public: private: DPSprite () {} - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); void Destroy(); diff --git a/src/p_pusher.cpp b/src/p_pusher.cpp index 4fb3b8f1f7..38c9c9720f 100644 --- a/src/p_pusher.cpp +++ b/src/p_pusher.cpp @@ -26,7 +26,7 @@ #include #include "actor.h" #include "p_spec.h" -#include "farchive.h" +#include "serializer.h" #include "p_lnspec.h" #include "c_cvars.h" #include "p_maputl.h" @@ -52,7 +52,7 @@ public: DPusher (); DPusher (EPusher type, line_t *l, int magnitude, int angle, AActor *source, int affectee); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); int CheckForSectorMatch (EPusher type, int tag); void ChangeValues (int magnitude, int angle) { @@ -83,23 +83,15 @@ DPusher::DPusher () { } -inline FArchive &operator<< (FArchive &arc, DPusher::EPusher &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (DPusher::EPusher)val; - return arc; -} - -void DPusher::Serialize (FArchive &arc) +void DPusher::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_Source - << m_PushVec - << m_Magnitude - << m_Radius - << m_Affectee; + arc.Enum("type", m_Type) + ("source", m_Source) + ("pushvec", m_PushVec) + ("magnitude", m_Magnitude) + ("radius", m_Radius) + ("affectee", m_Affectee); } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 54046d6a4b..170273b431 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -3,7 +3,8 @@ ** Code for serializing the world state in an archive ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2016 Randy Heit +** Copyright 2005-2016 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -51,28 +52,506 @@ #include "po_man.h" #include "p_setup.h" #include "r_data/colormaps.h" -#include "farchive.h" #include "p_lnspec.h" #include "p_acs.h" #include "p_terrain.h" +#include "am_map.h" +#include "r_data/r_translate.h" +#include "sbar.h" +#include "r_utility.h" +#include "r_sky.h" +#include "r_renderer.h" +#include "serializer.h" -void CopyPlayer (player_t *dst, player_t *src, const char *name); -static void ReadOnePlayer (FArchive &arc, bool skipload); -static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNow, bool skipload); -static void SpawnExtraPlayers (); -inline FArchive &operator<< (FArchive &arc, FLinkedSector &link) +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, line_t &line, line_t *def) { - arc << link.Sector << link.Type; + if (arc.BeginObject(key)) + { + arc("flags", line.flags, def->flags) + ("activation", line.activation, def->activation) + ("special", line.special, def->special) + ("alpha", line.alpha, def->alpha) + .Args("args", line.args, def->args, line.special) + ("portalindex", line.portalindex, def->portalindex) + // Unless the map loader is changed the sidedef references will not change between map loads so there's no need to save them. + //.Array("sides", line.sidedef, 2) + .EndObject(); + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, side_t::part &part, side_t::part *def) +{ + if (arc.canSkip() && def != nullptr && !memcmp(&part, def, sizeof(part))) + { + return arc; + } + + if (arc.BeginObject(key)) + { + arc("xoffset", part.xOffset, def->xOffset) + ("yoffset", part.yOffset, def->yOffset) + ("xscale", part.xScale, def->xScale) + ("yscale", part.yScale, def->yScale) + ("texture", part.texture, def->texture) + ("interpolation", part.interpolation) + .EndObject(); + } return arc; } +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, side_t &side, side_t *def) +{ + if (arc.BeginObject(key)) + { + arc.Array("textures", side.textures, def->textures, 3, true) + ("light", side.Light, def->Light) + ("flags", side.Flags, def->Flags) + // These also remain identical across map loads + //("leftside", side.LeftSide) + //("rightside", side.RightSide) + //("index", side.Index) + ("attacheddecals", side.AttachedDecals) + .EndObject(); + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FLinkedSector &ls, FLinkedSector *def) +{ + if (arc.BeginObject(key)) + { + arc("sector", ls.Sector) + ("type", ls.Type) + .EndObject(); + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, sector_t::splane &p, sector_t::splane *def) +{ + if (arc.canSkip() && def != nullptr && !memcmp(&p, def, sizeof(p))) + { + return arc; + } + + if (arc.BeginObject(key)) + { + arc("xoffs", p.xform.xOffs, def->xform.xOffs) + ("yoffs", p.xform.yOffs, def->xform.yOffs) + ("xscale", p.xform.xScale, def->xform.xScale) + ("yscale", p.xform.yScale, def->xform.yScale) + ("angle", p.xform.Angle, def->xform.Angle) + ("baseyoffs", p.xform.baseyOffs, def->xform.baseyOffs) + ("baseangle", p.xform.baseAngle, def->xform.baseAngle) + ("flags", p.Flags, def->Flags) + ("light", p.Light, def->Light) + ("texture", p.Texture, def->Texture) + ("texz", p.TexZ, def->TexZ) + ("alpha", p.alpha, def->alpha) + .EndObject(); + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def) +{ + if (arc.canSkip() && def != nullptr && !memcmp(&p, def, sizeof(p))) + { + return arc; + } + + if (arc.BeginObject(key)) + { + Serialize(arc, "normal", p.normal, def ? &def->normal : nullptr); + Serialize(arc, "d", p.D, def ? &def->D : nullptr); + arc.EndObject(); + + if (arc.isReading() && p.normal.Z != 0) + { + p.negiC = -1 / p.normal.Z; + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, sector_t &p, sector_t *def) +{ + // save the Scroll data here because it's a lot easier to handle a default. + // Just writing out the full array can massively inflate the archive for no gain. + DVector2 scroll = { 0,0 }, nul = { 0,0 }; + if (arc.isWriting() && level.Scrolls.Size() > 0) scroll = level.Scrolls[p.sectornum]; + + if (arc.BeginObject(key)) + { + arc("floorplane", p.floorplane, def->floorplane) + ("ceilingplane", p.ceilingplane, def->ceilingplane) + ("lightlevel", p.lightlevel, def->lightlevel) + ("special", p.special, def->special) + ("seqtype", p.seqType, def->seqType) + ("seqname", p.SeqName, def->SeqName) + ("friction", p.friction, def->friction) + ("movefactor", p.movefactor, def->movefactor) + ("stairlock", p.stairlock, def->stairlock) + ("prevsec", p.prevsec, def->prevsec) + ("nextsec", p.nextsec, def->nextsec) + .Array("planes", p.planes, def->planes, 2, true) + // These cannot change during play. + //("heightsec", p.heightsec) + //("bottommap", p.bottommap) + //("midmap", p.midmap) + //("topmap", p.topmap) + ("damageamount", p.damageamount, def->damageamount) + ("damageinterval", p.damageinterval, def->damageinterval) + ("leakydamage", p.leakydamage, def->leakydamage) + ("damagetype", p.damagetype, def->damagetype) + ("sky", p.sky, def->sky) + ("moreflags", p.MoreFlags, def->MoreFlags) + ("flags", p.Flags, def->Flags) + .Array("portals", p.Portals, def->Portals, 2, true) + ("zonenumber", p.ZoneNumber, def->ZoneNumber) + .Array("interpolations", p.interpolations, 4, true) + ("soundtarget", p.SoundTarget) + ("secacttarget", p.SecActTarget) + ("floordata", p.floordata) + ("ceilingdata", p.ceilingdata) + ("lightingdata", p.lightingdata) + ("fakefloor_sectors", p.e->FakeFloor.Sectors) + ("midtexf_lines", p.e->Midtex.Floor.AttachedLines) + ("midtexf_sectors", p.e->Midtex.Floor.AttachedSectors) + ("midtexc_lines", p.e->Midtex.Ceiling.AttachedLines) + ("midtexc_sectors", p.e->Midtex.Ceiling.AttachedSectors) + ("linked_floor", p.e->Linked.Floor.Sectors) + ("linked_ceiling", p.e->Linked.Ceiling.Sectors) + ("colormap", p.ColorMap, def->ColorMap) + .Terrain("floorterrain", p.terrainnum[0], &def->terrainnum[0]) + .Terrain("ceilingterrain", p.terrainnum[1], &def->terrainnum[1]) + ("scrolls", scroll, nul) + // GZDoom exclusive: + .Array("reflect", p.reflect, def->reflect, 2) + .EndObject(); + + if (arc.isReading() && !scroll.isZero()) + { + if (level.Scrolls.Size() == 0) + { + level.Scrolls.Resize(numsectors); + memset(&level.Scrolls[0], 0, sizeof(level.Scrolls[0])*level.Scrolls.Size()); + level.Scrolls[p.sectornum] = scroll; + } + } + } + return arc; +} + +//========================================================================== +// +// RecalculateDrawnSubsectors +// +// In case the subsector data is unusable this function tries to reconstruct +// if from the linedefs' ML_MAPPED info. +// +//========================================================================== + +void RecalculateDrawnSubsectors() +{ + for (int i = 0; inumlines; j++) + { + if (sub->firstline[j].linedef != NULL && + (sub->firstline[j].linedef->flags & ML_MAPPED)) + { + sub->flags |= SSECF_DRAWN; + } + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, subsector_t *&ss, subsector_t **) +{ + BYTE by; + const char *str; + + if (arc.isWriting()) + { + if (hasglnodes) + { + TArray encoded(1 + (numsubsectors + 5) / 6); + int p = 0; + for (int i = 0; i < numsubsectors; i += 6) + { + by = 0; + for (int j = 0; j < 6; j++) + { + if (i + j < numsubsectors && (subsectors[i + j].flags & SSECF_DRAWN)) + { + by |= (1 << j); + } + } + if (by < 10) by += '0'; + else if (by < 36) by += 'A' - 10; + else if (by < 62) by += 'a' - 36; + else if (by == 62) by = '-'; + else if (by == 63) by = '+'; + encoded[p++] = by; + } + encoded[p] = 0; + str = &encoded[0]; + if (arc.BeginArray(key)) + { + arc(nullptr, numvertexes) + (nullptr, numsubsectors) + .StringPtr(nullptr, str) + .EndArray(); + } + } + } + else + { + int num_verts, num_subs; + bool success = false; + if (arc.BeginArray(key)) + { + arc(nullptr, num_verts) + (nullptr, num_subs) + .StringPtr(nullptr, str) + .EndArray(); + + if (num_verts == numvertexes && num_subs == numsubsectors && hasglnodes) + { + success = true; + int sub = 0; + for (int i = 0; str[i] != 0; i++) + { + by = str[i]; + if (by >= '0' && by <= '9') by -= '0'; + else if (by >= 'A' && by <= 'Z') by -= 'A' - 10; + else if (by >= 'a' && by <= 'z') by -= 'a' - 36; + else if (by == '-') by = 62; + else if (by == '+') by = 63; + else + { + success = false; + break; + } + for (int s = 0; s < 6; s++) + { + if (sub + s < numsubsectors && (by & (1 << s))) + { + subsectors[sub + s].flags |= SSECF_DRAWN; + } + } + sub += 6; + } + } + if (hasglnodes && !success) + { + RecalculateDrawnSubsectors(); + } + } + + } + return arc; +} + +//============================================================================ +// +// Save a line portal for savegames. +// +//============================================================================ + +FSerializer &Serialize(FSerializer &arc, const char *key, FLinePortal &port, FLinePortal *def) +{ + if (arc.BeginObject(key)) + { + arc("origin", port.mOrigin) + ("destination", port.mDestination) + ("displacement", port.mDisplacement) + ("type", port.mType) + ("flags", port.mFlags) + ("defflags", port.mDefFlags) + ("align", port.mAlign) + .EndObject(); + } + return arc; +} + +//============================================================================ +// +// Save a sector portal for savegames. +// +//============================================================================ + +FSerializer &Serialize(FSerializer &arc, const char *key, FSectorPortal &port, FSectorPortal *def) +{ + if (arc.BeginObject(key)) + { + arc("type", port.mType) + ("flags", port.mFlags) + ("partner", port.mPartner) + ("plane", port.mPlane) + ("origin", port.mOrigin) + ("destination", port.mDestination) + ("displacement", port.mDisplacement) + ("planez", port.mPlaneZ) + ("skybox", port.mSkybox) + .EndObject(); + } + return arc; +} + +//============================================================================ +// +// one polyobject. +// +//============================================================================ + +FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj &poly, FPolyObj *def) +{ + if (arc.BeginObject(key)) + { + DAngle angle = poly.Angle; + DVector2 delta = poly.StartSpot.pos; + arc("angle", angle) + ("pos", delta) + ("interpolation", poly.interpolation) + ("blocked", poly.bBlocked) + ("hasportals", poly.bHasPortals) + ("specialdata", poly.specialdata) + .EndObject(); + + if (arc.isReading()) + { + poly.RotatePolyobj(angle, true); + delta -= poly.StartSpot.pos; + poly.MovePolyobj(delta, true); + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, ReverbContainer *&c, ReverbContainer **def) +{ + int id = (arc.isReading() || c == nullptr) ? 0 : c->ID; + Serialize(arc, key, id, nullptr); + if (arc.isReading()) + { + c = S_FindEnvironment(id); + } + return arc; +} + +FSerializer &Serialize(FSerializer &arc, const char *key, zone_t &z, zone_t *def) +{ + return Serialize(arc, key, z.Environment, nullptr); +} + +//========================================================================== +// +// ArchiveSounds +// +//========================================================================== + +void P_SerializeSounds(FSerializer &arc) +{ + S_SerializeSounds(arc); + DSeqNode::SerializeSequences (arc); + const char *name = NULL; + BYTE order; + + if (arc.isWriting()) + { + order = S_GetMusic(&name); + } + arc.StringPtr("musicname", name) + ("musicorder", order); + + if (arc.isReading()) + { + if (!S_ChangeMusic(name, order)) + if (level.cdtrack == 0 || !S_ChangeCDMusic(level.cdtrack, level.cdid)) + S_ChangeMusic(level.Music, level.musicorder); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void CopyPlayer(player_t *dst, player_t *src, const char *name); +static void ReadOnePlayer(FSerializer &arc, bool skipload); +static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload); +static void SpawnExtraPlayers(); + +//========================================================================== // // P_ArchivePlayers // -void P_SerializePlayers (FArchive &arc, bool skipload) +//========================================================================== + +void P_SerializePlayers(FSerializer &arc, bool skipload) { - BYTE numPlayers, numPlayersNow; + int numPlayers, numPlayersNow; int i; // Count the number of players present right now. @@ -84,84 +563,111 @@ void P_SerializePlayers (FArchive &arc, bool skipload) } } - if (arc.IsStoring()) + if (arc.isWriting()) { // Record the number of players in this save. - arc << numPlayersNow; - - // Record each player's name, followed by their data. - for (i = 0; i < MAXPLAYERS; ++i) + arc("numplayers", numPlayersNow); + if (arc.BeginArray("players")) { - if (playeringame[i]) + // Record each player's name, followed by their data. + for (i = 0; i < MAXPLAYERS; ++i) { - arc.WriteString (players[i].userinfo.GetName()); - players[i].Serialize (arc); + if (playeringame[i]) + { + if (arc.BeginObject(nullptr)) + { + const char *n = players[i].userinfo.GetName(); + arc.StringPtr("playername", n); + players[i].Serialize(arc); + arc.EndObject(); + } + } } + arc.EndArray(); } } else { - arc << numPlayers; + arc("numplayers", numPlayers); - // If there is only one player in the game, they go to the - // first player present, no matter what their name. - if (numPlayers == 1) + if (arc.BeginArray("players")) { - ReadOnePlayer (arc, skipload); - } - else - { - ReadMultiplePlayers (arc, numPlayers, numPlayersNow, skipload); + // If there is only one player in the game, they go to the + // first player present, no matter what their name. + if (numPlayers == 1) + { + ReadOnePlayer(arc, skipload); + } + else + { + ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload); + } + arc.EndArray(); } if (!skipload && numPlayersNow > numPlayers) { - SpawnExtraPlayers (); + SpawnExtraPlayers(); } // Redo pitch limits, since the spawned player has them at 0. players[consoleplayer].SendPitchLimits(); } } -static void ReadOnePlayer (FArchive &arc, bool skipload) +//========================================================================== +// +// +// +//========================================================================== + +static void ReadOnePlayer(FSerializer &arc, bool skipload) { int i; - char *name = NULL; + const char *name = NULL; bool didIt = false; - arc << name; - - for (i = 0; i < MAXPLAYERS; ++i) + if (arc.BeginObject(nullptr)) { - if (playeringame[i]) + arc.StringPtr("playername", name); + + for (i = 0; i < MAXPLAYERS; ++i) { - if (!didIt) + if (playeringame[i]) { - didIt = true; - player_t playerTemp; - playerTemp.Serialize (arc); - if (!skipload) + if (!didIt) { - CopyPlayer (&players[i], &playerTemp, name); + didIt = true; + player_t playerTemp; + playerTemp.Serialize(arc); + if (!skipload) + { + CopyPlayer(&players[i], &playerTemp, name); + } } - } - else - { - if (players[i].mo != NULL) + else { - players[i].mo->Destroy(); - players[i].mo = NULL; + if (players[i].mo != NULL) + { + players[i].mo->Destroy(); + players[i].mo = NULL; + } } } } + arc.EndObject(); } - delete[] name; } -static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNow, bool skipload) +//========================================================================== +// +// +// +//========================================================================== + +static void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload) { // For two or more players, read each player into a temporary array. int i, j; - char **nametemp = new char *[numPlayers]; + const char **nametemp = new const char *[numPlayers]; player_t *playertemp = new player_t[numPlayers]; BYTE *tempPlayerUsed = new BYTE[numPlayers]; BYTE playerUsed[MAXPLAYERS]; @@ -169,8 +675,12 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo for (i = 0; i < numPlayers; ++i) { nametemp[i] = NULL; - arc << nametemp[i]; - playertemp[i].Serialize (arc); + if (arc.BeginObject(nullptr)) + { + arc.StringPtr("playername", nametemp[i]); + playertemp[i].Serialize(arc); + arc.EndObject(); + } tempPlayerUsed[i] = 0; } for (i = 0; i < MAXPLAYERS; ++i) @@ -190,8 +700,8 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo { if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0) { // Found a match, so copy our temp player to the real player - Printf ("Found player %d (%s) at %d\n", i, nametemp[i], j); - CopyPlayer (&players[j], &playertemp[i], nametemp[i]); + Printf("Found player %d (%s) at %d\n", i, nametemp[i], j); + CopyPlayer(&players[j], &playertemp[i], nametemp[i]); playerUsed[j] = 1; tempPlayerUsed[i] = 1; break; @@ -209,8 +719,8 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo { if (playerUsed[j] == 0) { - Printf ("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName()); - CopyPlayer (&players[j], &playertemp[i], nametemp[i]); + Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName()); + CopyPlayer(&players[j], &playertemp[i], nametemp[i]); playerUsed[j] = 1; tempPlayerUsed[i] = 1; break; @@ -248,14 +758,16 @@ static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNo delete[] tempPlayerUsed; delete[] playertemp; - for (i = 0; i < numPlayers; ++i) - { - delete[] nametemp[i]; - } delete[] nametemp; } -void CopyPlayer (player_t *dst, player_t *src, const char *name) +//========================================================================== +// +// +// +//========================================================================== + +void CopyPlayer(player_t *dst, player_t *src, const char *name) { // The userinfo needs to be saved for real players, but it // needs to come from the save for bots. @@ -269,7 +781,7 @@ void CopyPlayer (player_t *dst, player_t *src, const char *name) bool attackdown = dst->attackdown; bool usedown = dst->usedown; - + *dst = *src; // To avoid memory leaks at this point the userinfo in src must be empty which is taken care of by the TransferFrom call above. dst->cheats |= chasecam; @@ -277,7 +789,7 @@ void CopyPlayer (player_t *dst, player_t *src, const char *name) if (dst->Bot != nullptr) { botinfo_t *thebot = bglobal.botinfo; - while (thebot && stricmp (name, thebot->name)) + while (thebot && stricmp(name, thebot->name)) { thebot = thebot->next; } @@ -318,7 +830,13 @@ void CopyPlayer (player_t *dst, player_t *src, const char *name) dst->usedown = usedown; } -static void SpawnExtraPlayers () +//========================================================================== +// +// +// +//========================================================================== + +static void SpawnExtraPlayers() { // If there are more players now than there were in the savegame, // be sure to spawn the extra players. @@ -339,390 +857,115 @@ static void SpawnExtraPlayers () } } +//============================================================================ // -// P_ArchiveWorld // -void P_SerializeWorld (FArchive &arc) -{ - int i, j; - sector_t *sec; - line_t *li; - zone_t *zn; - - // do sectors - for (i = 0, sec = sectors; i < numsectors; i++, sec++) - { - arc << sec->floorplane - << sec->ceilingplane; - arc << sec->lightlevel; - arc << sec->special; - arc << sec->soundtraversed - << sec->seqType - << sec->friction - << sec->movefactor - << sec->stairlock - << sec->prevsec - << sec->nextsec - << sec->planes[sector_t::floor] - << sec->planes[sector_t::ceiling] - << sec->heightsec - << sec->bottommap << sec->midmap << sec->topmap - << sec->gravity; - P_SerializeTerrain(arc, sec->terrainnum[0]); - P_SerializeTerrain(arc, sec->terrainnum[1]); - arc << sec->damageamount; - arc << sec->damageinterval - << sec->leakydamage - << sec->damagetype - << sec->sky - << sec->MoreFlags - << sec->Flags - << sec->Portals[sector_t::floor] << sec->Portals[sector_t::ceiling] - << sec->ZoneNumber; - arc << sec->interpolations[0] - << sec->interpolations[1] - << sec->interpolations[2] - << sec->interpolations[3] - << sec->SeqName; - - sec->e->Serialize(arc); - if (arc.IsStoring ()) - { - arc << sec->ColorMap->Color - << sec->ColorMap->Fade; - BYTE sat = sec->ColorMap->Desaturate; - arc << sat; - } - else - { - PalEntry color, fade; - BYTE desaturate; - arc << color << fade - << desaturate; - sec->ColorMap = GetSpecialLights (color, fade, desaturate); - } - // begin of GZDoom additions - arc << sec->reflect[sector_t::ceiling] << sec->reflect[sector_t::floor]; - // end of GZDoom additions - } - - // do lines - for (i = 0, li = lines; i < numlines; i++, li++) - { - arc << li->flags - << li->activation - << li->special - << li->alpha; - - if (P_IsACSSpecial(li->special)) - { - P_SerializeACSScriptNumber(arc, li->args[0], false); - } - else - { - arc << li->args[0]; - } - arc << li->args[1] << li->args[2] << li->args[3] << li->args[4]; - - arc << li->portalindex; - arc << li->portaltransferred; // GZDoom addition. - - for (j = 0; j < 2; j++) - { - if (li->sidedef[j] == NULL) - continue; - - side_t *si = li->sidedef[j]; - arc << si->textures[side_t::top] - << si->textures[side_t::mid] - << si->textures[side_t::bottom] - << si->Light - << si->Flags - << si->LeftSide - << si->RightSide - << si->Index; - } - } - - // do zones - arc << numzones; - - if (arc.IsLoading()) - { - if (zones != NULL) - { - delete[] zones; - } - zones = new zone_t[numzones]; - } - - for (i = 0, zn = zones; i < numzones; ++i, ++zn) - { - arc << zn->Environment; - } - - arc << linePortals << sectorPortals; - P_CollectLinkedPortals(); -} - -void P_SerializeWorldActors(FArchive &arc) -{ - int i; - sector_t *sec; - line_t *line; - - for (i = 0, sec = sectors; i < numsectors; i++, sec++) - { - arc << sec->SoundTarget - << sec->SecActTarget - << sec->floordata - << sec->ceilingdata - << sec->lightingdata; - } - for (auto &s : sectorPortals) - { - arc << s.mSkybox; - } - for (i = 0, line = lines; i < numlines; i++, line++) - { - for (int s = 0; s < 2; s++) - { - if (line->sidedef[s] != NULL) - { - DBaseDecal::SerializeChain(arc, &line->sidedef[s]->AttachedDecals); - } - } - } -} - -void extsector_t::Serialize(FArchive &arc) -{ - arc << FakeFloor.Sectors - << Midtex.Floor.AttachedLines - << Midtex.Floor.AttachedSectors - << Midtex.Ceiling.AttachedLines - << Midtex.Ceiling.AttachedSectors - << Linked.Floor.Sectors - << Linked.Ceiling.Sectors; -} - -FArchive &operator<< (FArchive &arc, side_t::part &p) -{ - arc << p.xOffset << p.yOffset << p.interpolation << p.texture - << p.xScale << p.yScale;// << p.Light; - return arc; -} - -FArchive &operator<< (FArchive &arc, sector_t::splane &p) -{ - arc << p.xform.xOffs << p.xform.yOffs << p.xform.xScale << p.xform.yScale - << p.xform.Angle << p.xform.baseyOffs << p.xform.baseAngle - << p.Flags << p.Light << p.Texture << p.TexZ << p.alpha; - return arc; -} - - -// -// Thinkers // +//============================================================================ -// -// P_ArchiveThinkers -// - -void P_SerializeThinkers (FArchive &arc, bool hubLoad) +void G_SerializeLevel(FSerializer &arc, bool hubload) { - arc.EnableThinkers(); - DImpactDecal::SerializeTime (arc); - DThinker::SerializeAll (arc, hubLoad); -} + int i = level.totaltime; -void P_DestroyThinkers(bool hubLoad) -{ - if (hubLoad) - DThinker::DestroyMostThinkers(); + if (arc.isWriting()) + { + arc.Array("checksum", level.md5, 16); + } else + { + // prevent bad things from happening by doing a check on the size of level arrays and the map's entire checksum. + // The old code happily tried to load savegames with any mismatch here, often causing meaningless errors + // deep down in the deserializer or just a crash if the few insufficient safeguards were not triggered. + BYTE chk[16] = { 0 }; + arc.Array("checksum", chk, 16); + if (arc.GetSize("linedefs") != numlines || + arc.GetSize("sidedefs") != numsides || + arc.GetSize("sectors") != numsectors || + arc.GetSize("polyobjs") != po_NumPolyobjs || + memcmp(chk, level.md5, 16)) + { + I_Error("Savegame is from a different level"); + } + } + arc("saveversion", SaveVersion); + + Renderer->StartSerialize(arc); + if (arc.isReading()) + { DThinker::DestroyAllThinkers(); + interpolator.ClearInterpolations(); + arc.ReadObjects(hubload); + } + + arc("level.flags", level.flags) + ("level.flags2", level.flags2) + ("level.fadeto", level.fadeto) + ("level.found_secrets", level.found_secrets) + ("level.found_items", level.found_items) + ("level.killed_monsters", level.killed_monsters) + ("level.total_secrets", level.total_secrets) + ("level.total_items", level.total_items) + ("level.total_monsters", level.total_monsters) + ("level.gravity", level.gravity) + ("level.aircontrol", level.aircontrol) + ("level.teamdamage", level.teamdamage) + ("level.maptime", level.maptime) + ("level.totaltime", i) + ("level.skytexture1", level.skytexture1) + ("level.skytexture2", level.skytexture2); + + // Hub transitions must keep the current total time + if (!hubload) + level.totaltime = i; + + if (arc.isReading()) + { + sky1texture = level.skytexture1; + sky2texture = level.skytexture2; + R_InitSkyMap(); + G_AirControlChanged(); + } + + + + // fixme: This needs to ensure it reads from the correct place. Should be one once there's enough of this code converted to JSON + + FBehavior::StaticSerializeModuleStates(arc); + // The order here is important: First world state, then portal state, then thinkers, and last polyobjects. + arc.Array("linedefs", lines, &loadlines[0], numlines); + arc.Array("sidedefs", sides, &loadsides[0], numsides); + arc.Array("sectors", sectors, &loadsectors[0], numsectors); + arc("zones", Zones); + arc("lineportals", linePortals); + arc("sectorportals", sectorPortals); + if (arc.isReading()) P_CollectLinkedPortals(); + + DThinker::SerializeThinkers(arc, !hubload); + arc.Array("polyobjs", polyobjs, po_NumPolyobjs); + arc("subsectors", subsectors); + StatusBar->SerializeMessages(arc); + AM_SerializeMarkers(arc); + FRemapTable::StaticSerializeTranslations(arc); + FCanvasTextureInfo::Serialize(arc); + P_SerializePlayers(arc, hubload); + P_SerializeSounds(arc); + + if (arc.isReading()) + { + for (int i = 0; i < numsectors; i++) + { + P_Recalculate3DFloors(§ors[i]); + } + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i] && players[i].mo != NULL) + { + players[i].mo->SetupWeaponSlots(); + } + } + } + Renderer->EndSerialize(arc); + } -//========================================================================== -// -// ArchiveSounds -// -//========================================================================== -void P_SerializeSounds (FArchive &arc) -{ - S_SerializeSounds (arc); - DSeqNode::SerializeSequences (arc); - char *name = NULL; - BYTE order; - - if (arc.IsStoring ()) - { - order = S_GetMusic (&name); - } - arc << name << order; - if (arc.IsLoading ()) - { - if (!S_ChangeMusic (name, order)) - if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid)) - S_ChangeMusic (level.Music, level.musicorder); - } - delete[] name; -} - -//========================================================================== -// -// ArchivePolyobjs -// -//========================================================================== -#define ASEG_POLYOBJS 104 - -void P_SerializePolyobjs (FArchive &arc) -{ - int i; - FPolyObj *po; - - if (arc.IsStoring ()) - { - int seg = ASEG_POLYOBJS; - arc << seg << po_NumPolyobjs; - for(i = 0, po = polyobjs; i < po_NumPolyobjs; i++, po++) - { - arc << po->tag << po->Angle << po->StartSpot.pos << po->interpolation << po->bBlocked << po->bHasPortals; - arc << po->specialdata; - } - } - else - { - int data; - DAngle angle; - DVector2 delta; - - arc << data; - if (data != ASEG_POLYOBJS) - I_Error ("Polyobject marker missing"); - - arc << data; - if (data != po_NumPolyobjs) - { - I_Error ("UnarchivePolyobjs: Bad polyobj count"); - } - for (i = 0, po = polyobjs; i < po_NumPolyobjs; i++, po++) - { - arc << data; - if (data != po->tag) - { - I_Error ("UnarchivePolyobjs: Invalid polyobj tag"); - } - arc << angle << delta << po->interpolation; - arc << po->bBlocked; - arc << po->bHasPortals; - if (SaveVersion >= 4548) - { - arc << po->specialdata; - } - - po->RotatePolyobj (angle, true); - delta -= po->StartSpot.pos; - po->MovePolyobj (delta, true); - } - } -} - -//========================================================================== -// -// RecalculateDrawnSubsectors -// -// In case the subsector data is unusable this function tries to reconstruct -// if from the linedefs' ML_MAPPED info. -// -//========================================================================== - -void RecalculateDrawnSubsectors() -{ - for(int i=0;inumlines;j++) - { - if (sub->firstline[j].linedef != NULL && - (sub->firstline[j].linedef->flags & ML_MAPPED)) - { - sub->flags |= SSECF_DRAWN; - } - } - } -} - -//========================================================================== -// -// ArchiveSubsectors -// -//========================================================================== - -void P_SerializeSubsectors(FArchive &arc) -{ - int num_verts, num_subs, num_nodes; - BYTE by; - - if (arc.IsStoring()) - { - if (hasglnodes) - { - arc << numvertexes << numsubsectors << numnodes; // These are only for verification - for(int i=0;i #include "actor.h" #include "p_spec.h" -#include "farchive.h" +#include "serializer.h" #include "p_lnspec.h" #include "r_data/r_interpolate.h" @@ -46,7 +46,7 @@ public: DScroller (double dx, double dy, const line_t *l, int control, int accel, EScrollPos scrollpos = EScrollPos::scw_all); void Destroy(); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); bool AffectsWall (int wallnum) const { return m_Type == EScroll::sc_side && m_Affectee == wallnum; } @@ -87,22 +87,6 @@ END_POINTERS // //----------------------------------------------------------------------------- -inline FArchive &operator<< (FArchive &arc, EScroll &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (EScroll)val; - return arc; -} - -inline FArchive &operator<< (FArchive &arc, EScrollPos &type) -{ - int val = (int)type; - arc << val; - type = (EScrollPos)val; - return arc; -} - EScrollPos operator &(EScrollPos one, EScrollPos two) { return EScrollPos(int(one) & int(two)); @@ -114,20 +98,20 @@ EScrollPos operator &(EScrollPos one, EScrollPos two) // //----------------------------------------------------------------------------- -void DScroller::Serialize (FArchive &arc) +void DScroller::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Type - << m_dx << m_dy - << m_Affectee - << m_Control - << m_LastHeight - << m_vdx << m_vdy - << m_Accel - << m_Parts - << m_Interpolations[0] - << m_Interpolations[1] - << m_Interpolations[2]; + arc.Enum("type", m_Type) + ("dx", m_dx) + ("dy", m_dy) + ("affectee", m_Affectee) + ("control", m_Control) + ("lastheight", m_LastHeight) + ("vdx", m_vdx) + ("vdy", m_vdy) + ("accel", m_Accel) + .Enum("parts", m_Parts) + .Array("interpolations", m_Interpolations, 3); } //----------------------------------------------------------------------------- @@ -236,8 +220,8 @@ void DScroller::Tick () // [RH] Don't actually carry anything here. That happens later. case EScroll::sc_carry: - level.Scrolls[m_Affectee].Scroll.X += dx; - level.Scrolls[m_Affectee].Scroll.Y += dy; + level.Scrolls[m_Affectee].X += dx; + level.Scrolls[m_Affectee].Y += dy; break; case EScroll::sc_carry_ceiling: // to be added later diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index ed80875434..5ed4cd1442 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -28,7 +28,7 @@ #include "nodebuild.h" #include "p_terrain.h" #include "po_man.h" -#include "farchive.h" +#include "serializer.h" #include "r_utility.h" #include "a_sharedglobal.h" #include "p_local.h" @@ -1053,14 +1053,18 @@ double sector_t::NextLowestFloorAt(double x, double y, double z, int flags, doub // //=========================================================================== -FArchive &operator<< (FArchive &arc, secspecial_t &p) -{ - arc << p.special - << p.damageamount - << p.damagetype - << p.damageinterval - << p.leakydamage - << p.Flags; + FSerializer &Serialize(FSerializer &arc, const char *key, secspecial_t &spec, secspecial_t *def) + { + if (arc.BeginObject(key)) + { + arc("special", spec.special) + ("damageamount", spec.damageamount) + ("damagetype", spec.damagetype) + ("damageinterval", spec.damageinterval) + ("leakydamage", spec.leakydamage) + ("flags", spec.Flags) + .EndObject(); + } return arc; } @@ -1101,17 +1105,6 @@ bool secplane_t::CopyPlaneIfValid (secplane_t *dest, const secplane_t *opp) cons return copy; } -FArchive &operator<< (FArchive &arc, secplane_t &plane) -{ - arc << plane.normal << plane.D; - if (plane.normal.Z != 0) - { // plane.c should always be non-0. Otherwise, the plane - // would be perfectly vertical. (But then, don't let this crash on a broken savegame...) - plane.negiC = -1 / plane.normal.Z; - } - return arc; -} - //========================================================================== // // P_AlignFlat diff --git a/src/p_setup.cpp b/src/p_setup.cpp index d8d95d2776..aafed854b0 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -124,6 +124,7 @@ glsegextra_t* glsegextras; int numsectors; sector_t* sectors; +TArray loadsectors; int numsubsectors; subsector_t* subsectors; @@ -133,12 +134,13 @@ node_t* nodes; int numlines; line_t* lines; +TArray loadlines; int numsides; side_t* sides; +TArray loadsides; -int numzones; -zone_t* zones; +TArray Zones; node_t * gamenodes; int numgamenodes; @@ -816,8 +818,7 @@ void P_FloodZones () P_FloodZone (§ors[i], z++); } } - numzones = z; - zones = new zone_t[z]; + Zones.Resize(z); reverb = S_FindEnvironment(level.DefaultEnvironment); if (reverb == NULL) { @@ -826,7 +827,7 @@ void P_FloodZones () } for (i = 0; i < z; ++i) { - zones[i].Environment = reverb; + Zones[i].Environment = reverb; } } @@ -3549,18 +3550,9 @@ void P_FreeLevelData () polyobjs = NULL; } po_NumPolyobjs = 0; - if (zones != NULL) - { - delete[] zones; - zones = NULL; - } - numzones = 0; + Zones.Clear(); P_FreeStrifeConversations (); - if (level.Scrolls != NULL) - { - delete[] level.Scrolls; - level.Scrolls = NULL; - } + level.Scrolls.Clear(); P_ClearUDMFKeys(); } @@ -3672,6 +3664,8 @@ void P_SetupLevel (const char *lumpname, int position) I_Error("Unable to open map '%s'\n", lumpname); } + // generate a checksum for the level, to be included and checked with savegames. + map->GetChecksum(level.md5); // find map num level.lumpnum = map->lumpnum; hasglnodes = false; @@ -4202,6 +4196,13 @@ void P_SetupLevel (const char *lumpname, int position) MapThingsUserDataIndex.Clear(); MapThingsUserData.Clear(); + loadsectors.Resize(numsectors); + memcpy(&loadsectors[0], sectors, numsectors * sizeof(sector_t)); + loadlines.Resize(numlines); + memcpy(&loadlines[0], lines, numlines * sizeof(line_t)); + loadsides.Resize(numsides); + memcpy(&loadsides[0], sides, numsides * sizeof(side_t)); + if (glsegextras != NULL) { delete[] glsegextras; diff --git a/src/p_spec.cpp b/src/p_spec.cpp index f9b056939e..901016c14f 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -62,7 +62,7 @@ #include "g_level.h" #include "v_font.h" #include "a_sharedglobal.h" -#include "farchive.h" +#include "serializer.h" #include "a_keys.h" #include "c_dispatch.h" #include "r_sky.h" @@ -648,7 +648,7 @@ class DLightTransfer : public DThinker DLightTransfer() {} public: DLightTransfer (sector_t *srcSec, int target, bool copyFloor); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: @@ -662,11 +662,13 @@ protected: IMPLEMENT_CLASS (DLightTransfer) -void DLightTransfer::Serialize (FArchive &arc) +void DLightTransfer::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << LastLight; - arc << Source << TargetTag << CopyFloor; + arc("lastlight", LastLight) + ("source", Source) + ("targettag", TargetTag) + ("copyfloor", CopyFloor); } DLightTransfer::DLightTransfer (sector_t *srcSec, int target, bool copyFloor) @@ -736,7 +738,7 @@ class DWallLightTransfer : public DThinker DWallLightTransfer() {} public: DWallLightTransfer (sector_t *srcSec, int target, BYTE flags); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: @@ -750,11 +752,13 @@ protected: IMPLEMENT_CLASS (DWallLightTransfer) -void DWallLightTransfer::Serialize (FArchive &arc) +void DWallLightTransfer::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << LastLight; - arc << Source << TargetID << Flags; + arc("lastlight", LastLight) + ("source", Source) + ("targetid", TargetID) + ("flags", Flags); } DWallLightTransfer::DWallLightTransfer (sector_t *srcSec, int target, BYTE flags) diff --git a/src/p_spec.h b/src/p_spec.h index d7abd4b424..41d47c7216 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -184,7 +184,7 @@ public: platRaiseAndStayLockout, }; - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); bool IsLift() const { return m_Type == platDownWaitUpStay || m_Type == platDownWaitUpStayStone; } @@ -241,7 +241,7 @@ public: DPillar (sector_t *sector, EPillar type, double speed, double height, double height2, int crush, bool hexencrush); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); void Destroy(); @@ -283,7 +283,7 @@ public: DDoor (sector_t *sector); DDoor (sector_t *sec, EVlDoor type, double speed, int delay, int lightTag, int topcountdown); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: EVlDoor m_Type; @@ -325,7 +325,7 @@ public: DAnimatedDoor (sector_t *sector); DAnimatedDoor (sector_t *sec, line_t *line, int speed, int delay, FDoorAnimation *anim); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); bool StartClosing (); @@ -405,7 +405,7 @@ public: DCeiling (sector_t *sec); DCeiling (sector_t *sec, double speed1, double speed2, int silent); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: @@ -508,7 +508,7 @@ public: DFloor (sector_t *sec); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); //protected: @@ -573,7 +573,7 @@ public: DElevator (sector_t *sec); void Destroy(); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: @@ -601,7 +601,7 @@ class DWaggleBase : public DMover public: DWaggleBase (sector_t *sec); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); protected: double m_OriginalDist; @@ -612,7 +612,6 @@ protected: double m_ScaleDelta; int m_Ticker; int m_State; - TObjPtr m_Interpolation; friend bool EV_StartWaggle (int tag, line_t *line, int height, int speed, int offset, int timer, bool ceiling); diff --git a/src/p_states.cpp b/src/p_states.cpp index 2e61a78440..1a8116e53b 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -34,7 +34,6 @@ ** */ #include "actor.h" -#include "farchive.h" #include "templates.h" #include "cmdlib.h" #include "i_system.h" @@ -47,71 +46,6 @@ // actor. States are archived by recording the actor they belong // to and the index into that actor's list of states. -// For NULL states, which aren't owned by any actor, the owner -// is recorded as AActor with the following state. AActor should -// never actually have this many states of its own, so this -// is (relatively) safe. - -#define NULL_STATE_INDEX 127 - -//========================================================================== -// -// -//========================================================================== - -FArchive &operator<< (FArchive &arc, FState *&state) -{ - PClassActor *info; - - if (arc.IsStoring ()) - { - if (state == NULL) - { - arc.UserWriteClass (RUNTIME_CLASS(AActor)); - arc.WriteCount (NULL_STATE_INDEX); - return arc; - } - - info = FState::StaticFindStateOwner (state); - - if (info != NULL) - { - arc.UserWriteClass (info); - arc.WriteCount ((DWORD)(state - info->OwnedStates)); - } - else - { - /* this was never working as intended. - I_Error ("Cannot find owner for state %p:\n" - "%s %c%c %3d [%p] -> %p", state, - sprites[state->sprite].name, - state->GetFrame() + 'A', - state->GetFullbright() ? '*' : ' ', - state->GetTics(), - state->GetAction(), - state->GetNextState()); - */ - } - } - else - { - PClassActor *info; - DWORD ofs; - - arc.UserReadClass(info); - ofs = arc.ReadCount (); - if (ofs == NULL_STATE_INDEX && info == RUNTIME_CLASS(AActor)) - { - state = NULL; - } - else - { - state = info->OwnedStates + ofs; - } - } - return arc; -} - //========================================================================== // // Find the actor that a state belongs to. diff --git a/src/p_switch.cpp b/src/p_switch.cpp index fdea7747ba..c60b0fdcda 100644 --- a/src/p_switch.cpp +++ b/src/p_switch.cpp @@ -46,7 +46,7 @@ #include "w_wad.h" #include "tarray.h" #include "cmdlib.h" -#include "farchive.h" +#include "serializer.h" #include "p_maputl.h" #include "p_spec.h" @@ -61,7 +61,7 @@ public: DActiveButton (); DActiveButton (side_t *, int, FSwitchDef *, const DVector2 &pos, bool flippable); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); side_t *m_Side; @@ -348,16 +348,45 @@ DActiveButton::DActiveButton (side_t *side, int Where, FSwitchDef *Switch, AdvanceFrame (); } +//========================================================================== +// +// operator<< +// +//========================================================================== + +template<> FSerializer &Serialize (FSerializer &arc, const char *key, FSwitchDef* &Switch, FSwitchDef **def) +{ + if (arc.isWriting()) + { + Serialize(arc, key, Switch->PreTexture, nullptr); + } + else + { + FTextureID tex; + tex.SetInvalid(); + Serialize(arc, key, tex, nullptr); + Switch = TexMan.FindSwitch(tex); + } + return arc; +} + //========================================================================== // // // //========================================================================== -void DActiveButton::Serialize (FArchive &arc) +void DActiveButton::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Side << m_Part << m_SwitchDef << m_Frame << m_Timer << bFlippable << m_Pos << bReturning; + arc("side", m_Side) + ("part", m_Part) + ("switchdef", m_SwitchDef) + ("frame", m_Frame) + ("timer", m_Timer) + ("fippable", bFlippable) + ("pos", m_Pos) + ("returning", bReturning); } //========================================================================== diff --git a/src/p_terrain.cpp b/src/p_terrain.cpp index ca5a8b998a..2504bad94e 100644 --- a/src/p_terrain.cpp +++ b/src/p_terrain.cpp @@ -46,7 +46,6 @@ #include "s_sound.h" #include "p_local.h" #include "templates.h" -#include "farchive.h" // MACROS ------------------------------------------------------------------ @@ -713,25 +712,15 @@ int P_FindTerrain (FName name) return -1; } -void P_SerializeTerrain(FArchive &arc, int &terrainnum) +FName P_GetTerrainName(int terrainnum) { - FName val; - if (arc.IsStoring()) + if (terrainnum < 0 || terrainnum >= (int)Terrains.Size()) { - if (terrainnum < 0 || terrainnum >= (int)Terrains.Size()) - { - val = NAME_Null; - } - else - { - val = Terrains[terrainnum].Name; - } - arc << val; + return NAME_Null; } else { - arc << val; - terrainnum = P_FindTerrain(val); - + return Terrains[terrainnum].Name; } } + diff --git a/src/p_terrain.h b/src/p_terrain.h index 2a15eb9764..694aa9f144 100644 --- a/src/p_terrain.h +++ b/src/p_terrain.h @@ -122,8 +122,7 @@ struct FTerrainDef extern TArray Splashes; extern TArray Terrains; -class FArchive; int P_FindTerrain(FName name); -void P_SerializeTerrain(FArchive &arc, int &terrainnum); +FName P_GetTerrainName(int terrainnum); #endif //__P_TERRAIN_H__ diff --git a/src/p_user.cpp b/src/p_user.cpp index b602432b33..0f3b6a516d 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -52,7 +52,7 @@ #include "g_level.h" #include "d_net.h" #include "gstrings.h" -#include "farchive.h" +#include "serializer.h" #include "r_renderer.h" #include "d_player.h" #include "r_utility.h" @@ -237,11 +237,6 @@ CCMD (playerclasses) // 16 pixels of bob #define MAXBOB 16. -FArchive &operator<< (FArchive &arc, player_t *&p) -{ - return arc.SerializePointer (players, (BYTE **)&p, sizeof(*players)); -} - // The player_t constructor. Since LogText is not a POD, we cannot just // memset it all to 0. player_t::player_t() @@ -631,29 +626,32 @@ END_POINTERS IMPLEMENT_CLASS (APlayerChunk) -void APlayerPawn::Serialize (FArchive &arc) +void APlayerPawn::Serialize(FSerializer &arc) { Super::Serialize (arc); + auto def = (APlayerPawn*)GetDefault(); - arc << JumpZ - << MaxHealth - << RunHealth - << SpawnMask - << ForwardMove1 - << ForwardMove2 - << SideMove1 - << SideMove2 - << ScoreIcon - << InvFirst - << InvSel - << MorphWeapon - << DamageFade - << PlayerFlags - << FlechetteType; - arc << GruntSpeed << FallingScreamMinSpeed << FallingScreamMaxSpeed; - arc << UseRange; - arc << AirCapacity; - arc << ViewHeight; + arc("jumpz", JumpZ, def->JumpZ) + ("maxhealth", MaxHealth, def->MaxHealth) + ("runhealth", RunHealth, def->RunHealth) + ("spawnmask", SpawnMask, def->SpawnMask) + ("forwardmove1", ForwardMove1, def->ForwardMove1) + ("forwardmove2", ForwardMove2, def->ForwardMove2) + ("sidemove1", SideMove1, def->SideMove1) + ("sidemove2", SideMove2, def->SideMove2) + ("scoreicon", ScoreIcon, def->ScoreIcon) + ("invfirst", InvFirst) + ("invsel", InvSel) + ("morphweapon", MorphWeapon, def->MorphWeapon) + ("damagefade", DamageFade, def->DamageFade) + ("playerflags", PlayerFlags, def->PlayerFlags) + ("flechettetype", FlechetteType, def->FlechetteType) + ("gruntspeed", GruntSpeed, def->GruntSpeed) + ("fallingscreammin", FallingScreamMinSpeed, def->FallingScreamMinSpeed) + ("fallingscreammaxn", FallingScreamMaxSpeed, def->FallingScreamMaxSpeed) + ("userange", UseRange, def->UseRange) + ("aircapacity", AirCapacity, def->AirCapacity) + ("viewheight", ViewHeight, def->ViewHeight); } //=========================================================================== @@ -3001,17 +2999,17 @@ void P_UnPredictPlayer () } } -void player_t::Serialize (FArchive &arc) +void player_t::Serialize(FSerializer &arc) { - int i; FString skinname; - arc << cls - << mo - << camera - << playerstate - << cmd; - if (arc.IsLoading()) + arc("class", cls) + ("mo", mo) + ("camera", camera) + ("playerstate", playerstate) + ("cmd", cmd); + + if (arc.isReading()) { ReadUserInfo(arc, userinfo, skinname); } @@ -3019,126 +3017,80 @@ void player_t::Serialize (FArchive &arc) { WriteUserInfo(arc, userinfo); } - arc << DesiredFOV << FOV - << viewz - << viewheight - << deltaviewheight - << bob - << Vel - << centering - << health - << inventorytics; - arc << fragcount - << spreecount - << multicount - << lastkilltime - << ReadyWeapon << PendingWeapon - << cheats - << refire - << inconsistant - << killcount - << itemcount - << secretcount - << damagecount - << bonuscount - << hazardcount - << poisoncount - << poisoner - << attacker - << extralight - << fixedcolormap << fixedlightlevel - << morphTics - << MorphedPlayerClass - << MorphStyle - << MorphExitFlash - << PremorphWeapon - << chickenPeck - << jumpTics - << respawn_time - << air_finished - << turnticks - << oldbuttons; - arc << hazardtype - << hazardinterval; - arc << Bot; - arc << BlendR - << BlendG - << BlendB - << BlendA; - arc << WeaponState; - arc << LogText - << ConversationNPC - << ConversationPC - << ConversationNPCAngle.Degrees - << ConversationFaceTalker; - for (i = 0; i < MAXPLAYERS; i++) - arc << frags[i]; + arc("desiredfov", DesiredFOV) + ("fov", FOV) + ("viewz", viewz) + ("viewheight", viewheight) + ("deltaviewheight", deltaviewheight) + ("bob", bob) + ("vel", Vel) + ("centering", centering) + ("health", health) + ("inventorytics", inventorytics) + ("fragcount", fragcount) + ("spreecount", spreecount) + ("multicount", multicount) + ("lastkilltime", lastkilltime) + ("readyweapon", ReadyWeapon) + ("pendingweapon", PendingWeapon) + ("cheats", cheats) + ("refire", refire) + ("inconsistant", inconsistant) + ("killcount", killcount) + ("itemcount", itemcount) + ("secretcount", secretcount) + ("damagecount", damagecount) + ("bonuscount", bonuscount) + ("hazardcount", hazardcount) + ("poisoncount", poisoncount) + ("poisoner", poisoner) + ("attacker", attacker) + ("extralight", extralight) + ("fixedcolormap", fixedcolormap) + ("fixedlightlevel", fixedlightlevel) + ("morphTics", morphTics) + ("morphedplayerclass", MorphedPlayerClass) + ("morphstyle", MorphStyle) + ("morphexitflash", MorphExitFlash) + ("premorphweapon", PremorphWeapon) + ("chickenpeck", chickenPeck) + ("jumptics", jumpTics) + ("respawntime", respawn_time) + ("airfinished", air_finished) + ("turnticks", turnticks) + ("oldbuttons", oldbuttons) + ("hazardtype", hazardtype) + ("hazardinterval", hazardinterval) + ("bot", Bot) + ("blendr", BlendR) + ("blendg", BlendG) + ("blendb", BlendB) + ("blenda", BlendA) + ("weaponstate", WeaponState) + ("logtext", LogText) + ("conversionnpc", ConversationNPC) + ("conversionpc", ConversationPC) + ("conversionnpcangle", ConversationNPCAngle) + ("conversionfacetalker", ConversationFaceTalker) + .Array("frags", frags, MAXPLAYERS) + ("psprites", psprites) + ("currentplayerclass", CurrentPlayerClass) + ("crouchfactor", crouchfactor) + ("crouching", crouching) + ("crouchdir", crouchdir) + ("crouchviewdelta", crouchviewdelta) + ("original_cmd", original_cmd) + ("original_oldbuttons", original_oldbuttons) + ("poisontype", poisontype) + ("poisonpaintype", poisonpaintype) + ("timefreezer", timefreezer) + ("settings_controller", settings_controller) + ("onground", onground) + ("musinfoactor", MUSINFOactor) + ("musinfotics", MUSINFOtics); - if (SaveVersion < 4547) - { - int layer = PSP_WEAPON; - for (i = 0; i < 5; i++) - { - FState *state; - int tics; - double sx, sy; - int sprite; - int frame; - - arc << state << tics - << sx << sy - << sprite << frame; - - if (state != nullptr && - ((layer < PSP_TARGETCENTER && ReadyWeapon != nullptr) || - (layer >= PSP_TARGETCENTER && mo->FindInventory(RUNTIME_CLASS(APowerTargeter), true)))) - { - DPSprite *pspr; - pspr = GetPSprite(PSPLayers(layer)); - pspr->State = state; - pspr->Tics = tics; - pspr->Sprite = sprite; - pspr->Frame = frame; - pspr->Owner = this; - - if (layer == PSP_FLASH) - { - pspr->x = 0; - pspr->y = 0; - } - else - { - pspr->x = sx; - pspr->y = sy; - } - } - - if (layer == PSP_WEAPON) - layer = PSP_FLASH; - else if (layer == PSP_FLASH) - layer = PSP_TARGETCENTER; - else - layer++; - } - } - else - arc << psprites; - - arc << CurrentPlayerClass; - - arc << crouchfactor - << crouching - << crouchdir - << crouchviewdelta - << original_cmd - << original_oldbuttons; - arc << poisontype << poisonpaintype; - arc << timefreezer; - arc << settings_controller; - arc << onground; - - if (arc.IsLoading ()) + if (arc.isWriting ()) { // If the player reloaded because they pressed +use after dying, we // don't want +use to still be down after the game is loaded. @@ -3149,7 +3101,6 @@ void player_t::Serialize (FArchive &arc) { userinfo.SkinChanged(skinname, CurrentPlayerClass); } - arc << MUSINFOactor << MUSINFOtics; } bool P_IsPlayerTotallyFrozen(const player_t *player) diff --git a/src/po_man.cpp b/src/po_man.cpp index e449e0d67a..2eed965969 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -27,7 +27,7 @@ #include "po_man.h" #include "p_setup.h" #include "vectors.h" -#include "farchive.h" +#include "serializer.h" #include "p_blockmap.h" #include "p_maputl.h" #include "r_utility.h" @@ -50,25 +50,6 @@ inline vertex_t *side_t::V2() const } -FArchive &operator<< (FArchive &arc, FPolyObj *&poly) -{ - return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); -} - -FArchive &operator<< (FArchive &arc, const FPolyObj *&poly) -{ - return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); -} - -inline FArchive &operator<< (FArchive &arc, podoortype_t &type) -{ - BYTE val = (BYTE)type; - arc << val; - type = (podoortype_t)val; - return arc; -} - - class DRotatePoly : public DPolyAction { DECLARE_CLASS (DRotatePoly, DPolyAction) @@ -87,7 +68,7 @@ class DMovePoly : public DPolyAction DECLARE_CLASS (DMovePoly, DPolyAction) public: DMovePoly (int polyNum); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: DMovePoly (); @@ -102,7 +83,7 @@ class DMovePolyTo : public DPolyAction DECLARE_CLASS(DMovePolyTo, DPolyAction) public: DMovePolyTo(int polyNum); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Tick(); protected: DMovePolyTo(); @@ -118,7 +99,7 @@ class DPolyDoor : public DMovePoly DECLARE_CLASS (DPolyDoor, DMovePoly) public: DPolyDoor (int polyNum, podoortype_t type); - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void Tick (); protected: DAngle m_Direction; @@ -199,10 +180,13 @@ DPolyAction::DPolyAction () { } -void DPolyAction::Serialize (FArchive &arc) +void DPolyAction::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_PolyObj << m_Speed << m_Dist << m_Interpolation; + arc("polyobj", m_PolyObj) + ("speed", m_Speed) + ("dist", m_Dist) + ("interpolation", m_Interpolation); } DPolyAction::DPolyAction (int polyNum) @@ -277,14 +261,12 @@ DMovePoly::DMovePoly () { } -void DMovePoly::Serialize (FArchive &arc) +void DMovePoly::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Angle << m_Speed; - if (SaveVersion >= 4548) - { - arc << m_Speedv; - } + arc("angle", m_Angle) + ("speed", m_Speed); + ("speedv", m_Speedv); } DMovePoly::DMovePoly (int polyNum) @@ -307,10 +289,11 @@ DMovePolyTo::DMovePolyTo() { } -void DMovePolyTo::Serialize(FArchive &arc) +void DMovePolyTo::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << m_Speedv << m_Target; + arc("speedv", m_Speedv) + ("target", m_Target); } DMovePolyTo::DMovePolyTo(int polyNum) @@ -331,10 +314,15 @@ DPolyDoor::DPolyDoor () { } -void DPolyDoor::Serialize (FArchive &arc) +void DPolyDoor::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Direction << m_TotalDist << m_Tics << m_WaitTics << m_Type << m_Close; + arc.Enum("type", m_Type) + ("direction", m_Direction) + ("totaldist", m_TotalDist) + ("tics", m_Tics) + ("waittics", m_WaitTics) + ("close", m_Close); } DPolyDoor::DPolyDoor (int polyNum, podoortype_t type) diff --git a/src/po_man.h b/src/po_man.h index 18b01d9cca..ec6a3901b9 100644 --- a/src/po_man.h +++ b/src/po_man.h @@ -11,7 +11,7 @@ class DPolyAction : public DThinker HAS_OBJECT_POINTERS public: DPolyAction(int polyNum); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void Destroy(); void Stop(); double GetSpeed() const { return m_Speed; } @@ -126,8 +126,6 @@ struct polyblock_t void PO_LinkToSubsectors(); -FArchive &operator<< (FArchive &arc, FPolyObj *&poly); -FArchive &operator<< (FArchive &arc, const FPolyObj *&poly); // ===== PO_MAN ===== diff --git a/src/portal.cpp b/src/portal.cpp index d7dad220ee..cef79820fe 100644 --- a/src/portal.cpp +++ b/src/portal.cpp @@ -45,7 +45,6 @@ #include "c_cvars.h" #include "m_bbox.h" #include "p_tags.h" -#include "farchive.h" #include "v_text.h" #include "a_sharedglobal.h" #include "i_system.h" @@ -197,48 +196,6 @@ void FLinePortalTraverse::AddLineIntercepts(int bx, int by) } } -//============================================================================ -// -// Save a line portal for savegames. -// -//============================================================================ - -FArchive &operator<< (FArchive &arc, FLinePortal &port) -{ - arc << port.mOrigin - << port.mDestination - << port.mDisplacement - << port.mType - << port.mFlags - << port.mDefFlags - << port.mAlign; - return arc; -} - -//============================================================================ -// -// Save a sector portal for savegames. -// -//============================================================================ - -FArchive &operator<< (FArchive &arc, FSectorPortal &port) -{ - arc << port.mType - << port.mFlags - << port.mPartner - << port.mPlane - << port.mOrigin - << port.mDestination - << port.mDisplacement - << port.mPlaneZ; - if (arc.IsLoading()) - { - port.mSkybox = nullptr; - } - return arc; -} - - //============================================================================ // // finds the destination for a line portal for spawning diff --git a/src/r_data/r_interpolate.cpp b/src/r_data/r_interpolate.cpp index 8a88abed21..e60223bac1 100644 --- a/src/r_data/r_interpolate.cpp +++ b/src/r_data/r_interpolate.cpp @@ -39,7 +39,7 @@ #include "p_local.h" #include "i_system.h" #include "po_man.h" -#include "farchive.h" +#include "serializer.h" //========================================================================== // @@ -66,7 +66,8 @@ public: void UpdateInterpolation(); void Restore(); void Interpolate(double smoothratio); - void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); size_t PointerSubstitution (DObject *old, DObject *notOld); size_t PropagateMark(); }; @@ -94,7 +95,8 @@ public: void UpdateInterpolation(); void Restore(); void Interpolate(double smoothratio); - void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); }; @@ -121,7 +123,8 @@ public: void UpdateInterpolation(); void Restore(); void Interpolate(double smoothratio); - void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); }; //========================================================================== @@ -147,7 +150,8 @@ public: void UpdateInterpolation(); void Restore(); void Interpolate(double smoothratio); - void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); }; @@ -370,11 +374,11 @@ void DInterpolation::Destroy() // //========================================================================== -void DInterpolation::Serialize(FArchive &arc) +void DInterpolation::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << refcount; - if (arc.IsLoading()) + arc("refcount", refcount); + if (arc.isReading()) { interpolator.AddInterpolation(this); } @@ -414,15 +418,18 @@ DSectorPlaneInterpolation::DSectorPlaneInterpolation(sector_t *_sector, bool _pl void DSectorPlaneInterpolation::Destroy() { - if (ceiling) + if (sector != nullptr) { - sector->interpolations[sector_t::CeilingMove] = NULL; + if (ceiling) + { + sector->interpolations[sector_t::CeilingMove] = nullptr; + } + else + { + sector->interpolations[sector_t::FloorMove] = nullptr; + } + sector = nullptr; } - else - { - sector->interpolations[sector_t::FloorMove] = NULL; - } - for(unsigned i=0; iDelRef(); @@ -516,10 +523,14 @@ void DSectorPlaneInterpolation::Interpolate(double smoothratio) // //========================================================================== -void DSectorPlaneInterpolation::Serialize(FArchive &arc) +void DSectorPlaneInterpolation::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << sector << ceiling << oldheight << oldtexz << attached; + arc("sector", sector) + ("ceiling", ceiling) + ("oldheight", oldheight) + ("oldtexz", oldtexz) + ("attached", attached); } //========================================================================== @@ -585,13 +596,17 @@ DSectorScrollInterpolation::DSectorScrollInterpolation(sector_t *_sector, bool _ void DSectorScrollInterpolation::Destroy() { - if (ceiling) + if (sector != nullptr) { - sector->interpolations[sector_t::CeilingScroll] = NULL; - } - else - { - sector->interpolations[sector_t::FloorScroll] = NULL; + if (ceiling) + { + sector->interpolations[sector_t::CeilingScroll] = nullptr; + } + else + { + sector->interpolations[sector_t::FloorScroll] = nullptr; + } + sector = nullptr; } Super::Destroy(); } @@ -648,10 +663,13 @@ void DSectorScrollInterpolation::Interpolate(double smoothratio) // //========================================================================== -void DSectorScrollInterpolation::Serialize(FArchive &arc) +void DSectorScrollInterpolation::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << sector << ceiling << oldx << oldy; + arc("sector", sector) + ("ceiling", ceiling) + ("oldx", oldx) + ("oldy", oldy); } @@ -683,7 +701,11 @@ DWallScrollInterpolation::DWallScrollInterpolation(side_t *_side, int _part) void DWallScrollInterpolation::Destroy() { - side->textures[part].interpolation = NULL; + if (side != nullptr) + { + side->textures[part].interpolation = nullptr; + side = nullptr; + } Super::Destroy(); } @@ -739,10 +761,13 @@ void DWallScrollInterpolation::Interpolate(double smoothratio) // //========================================================================== -void DWallScrollInterpolation::Serialize(FArchive &arc) +void DWallScrollInterpolation::Serialize(FSerializer &arc) { Super::Serialize(arc); - arc << side << part << oldx << oldy; + arc("side", side) + ("part", part) + ("oldx", oldx) + ("oldy", oldy); } //========================================================================== @@ -774,7 +799,10 @@ DPolyobjInterpolation::DPolyobjInterpolation(FPolyObj *po) void DPolyobjInterpolation::Destroy() { - poly->interpolation = NULL; + if (poly != nullptr) + { + poly->interpolation = nullptr; + } Super::Destroy(); } @@ -855,15 +883,14 @@ void DPolyobjInterpolation::Interpolate(double smoothratio) // //========================================================================== -void DPolyobjInterpolation::Serialize(FArchive &arc) +void DPolyobjInterpolation::Serialize(FSerializer &arc) { Super::Serialize(arc); - int po = int(poly - polyobjs); - arc << po << oldverts; - poly = polyobjs + po; - - arc << oldcx << oldcy; - if (arc.IsLoading()) bakverts.Resize(oldverts.Size()); + arc("poly", poly) + ("oldverts", oldverts) + ("oldcx", oldcx) + ("oldcy", oldcy); + if (arc.isReading()) bakverts.Resize(oldverts.Size()); } diff --git a/src/r_data/r_interpolate.h b/src/r_data/r_interpolate.h index e49500e571..092cbe5eec 100644 --- a/src/r_data/r_interpolate.h +++ b/src/r_data/r_interpolate.h @@ -31,7 +31,8 @@ public: virtual void UpdateInterpolation() = 0; virtual void Restore() = 0; virtual void Interpolate(double smoothratio) = 0; - virtual void Serialize(FArchive &arc); + + virtual void Serialize(FSerializer &arc); }; //========================================================================== diff --git a/src/r_data/r_translate.cpp b/src/r_data/r_translate.cpp index cc923f1891..1ee5e0b063 100644 --- a/src/r_data/r_translate.cpp +++ b/src/r_data/r_translate.cpp @@ -46,8 +46,10 @@ #include "i_system.h" #include "w_wad.h" #include "r_data/colormaps.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" +#include "r_data/sprites.h" +#include "r_state.h" #include "gi.h" #include "stats.h" @@ -192,27 +194,62 @@ bool FRemapTable::operator==(const FRemapTable &o) // //---------------------------------------------------------------------------- -void FRemapTable::Serialize(FArchive &arc) +void FRemapTable::Serialize(FSerializer &arc) { int n = NumEntries; - arc << NumEntries; - if (arc.IsStoring()) - { - arc.Write (Remap, NumEntries); - } - else + arc("numentries", NumEntries); + if (arc.isReading()) { if (n != NumEntries) { Free(); Alloc(NumEntries); } - arc.Read (Remap, NumEntries); } - for (int j = 0; j < NumEntries; ++j) + arc.Array("remap", Remap, NumEntries); + arc.Array("palette", Palette, NumEntries); +} + +void FRemapTable::StaticSerializeTranslations(FSerializer &arc) +{ + if (arc.BeginArray("translations")) { - arc << Palette[j]; + // Does this level have custom translations? + FRemapTable *trans; + int w; + if (arc.isWriting()) + { + for (unsigned int i = 0; i < translationtables[TRANSLATION_LevelScripted].Size(); ++i) + { + trans = translationtables[TRANSLATION_LevelScripted][i]; + if (trans != NULL && !trans->IsIdentity()) + { + if (arc.BeginObject(nullptr)) + { + arc("index", i); + trans->Serialize(arc); + arc.EndObject(); + } + } + } + } + else + { + while (arc.BeginObject(nullptr)) + { + arc("index", w); + trans = translationtables[TRANSLATION_LevelScripted].GetVal(w); + if (trans == NULL) + { + trans = new FRemapTable; + translationtables[TRANSLATION_LevelScripted].SetVal(w, trans); + } + trans->Serialize(arc); + arc.EndObject(); + } + } + arc.EndArray(); } } diff --git a/src/r_data/r_translate.h b/src/r_data/r_translate.h index 7df9dcb6d4..a549f07599 100644 --- a/src/r_data/r_translate.h +++ b/src/r_data/r_translate.h @@ -5,7 +5,7 @@ #include "tarray.h" class FNativePalette; -class FArchive; +class FSerializer; enum { @@ -36,7 +36,8 @@ struct FRemapTable void UpdateNative(); FNativePalette *GetNative(); bool IsIdentity() const; - void Serialize(FArchive &ar); + void Serialize(FSerializer &arc); + static void StaticSerializeTranslations(FSerializer &arc); void AddIndexRange(int start, int end, int pal1, int pal2); void AddColorRange(int start, int end, int r1,int g1, int b1, int r2, int g2, int b2); void AddDesaturation(int start, int end, double r1, double g1, double b1, double r2, double g2, double b2); diff --git a/src/r_data/renderstyle.cpp b/src/r_data/renderstyle.cpp index fdd20aa17a..6e656f715a 100644 --- a/src/r_data/renderstyle.cpp +++ b/src/r_data/renderstyle.cpp @@ -32,7 +32,6 @@ ** */ -#include "farchive.h" #include "templates.h" #include "renderstyle.h" #include "c_cvars.h" @@ -99,12 +98,6 @@ static struct LegacyInit #endif -FArchive &operator<< (FArchive &arc, FRenderStyle &style) -{ - arc << style.BlendOp << style.SrcAlpha << style.DestAlpha << style.Flags; - return arc; -} - double GetAlpha(int type, double alpha) { switch (type) diff --git a/src/r_data/renderstyle.h b/src/r_data/renderstyle.h index c103610eff..501c14c5f9 100644 --- a/src/r_data/renderstyle.h +++ b/src/r_data/renderstyle.h @@ -34,6 +34,7 @@ **--------------------------------------------------------------------------- ** */ +#include // also #defines OPAQUE #ifdef OPAQUE @@ -130,15 +131,15 @@ union FRenderStyle { struct { - BYTE BlendOp; // Of ERenderOp type - BYTE SrcAlpha; // Of ERenderAlpha type - BYTE DestAlpha; // Of ERenderAlpha type - BYTE Flags; + uint8_t BlendOp; // Of ERenderOp type + uint8_t SrcAlpha; // Of ERenderAlpha type + uint8_t DestAlpha; // Of ERenderAlpha type + uint8_t Flags; }; - uint32 AsDWORD; + uint32_t AsDWORD; inline FRenderStyle &operator= (ERenderStyle legacy); - operator uint32() const { return AsDWORD; } + operator uint32_t() const { return AsDWORD; } bool operator==(const FRenderStyle &o) const { return AsDWORD == o.AsDWORD; } void CheckFuzz(); bool IsVisible(double alpha) const throw(); @@ -162,8 +163,4 @@ inline FRenderStyle &FRenderStyle::operator= (ERenderStyle legacy) return *this; } -class FArchive; - -FArchive &operator<< (FArchive &arc, FRenderStyle &style); - #endif diff --git a/src/r_defs.h b/src/r_defs.h index 50d514fc5b..97552fb524 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -184,7 +184,6 @@ class FScanner; class FBitmap; struct FCopyInfo; class DInterpolation; -class FArchive; enum { @@ -297,7 +296,6 @@ class ASkyViewpoint; struct secplane_t { - friend FArchive &operator<< (FArchive &arc, secplane_t &plane); // the plane is defined as a*x + b*y + c*z + d = 0 // ic is 1/c, for faster Z calculations @@ -305,6 +303,7 @@ struct secplane_t DVector3 normal; double D, negiC; // negative iC because that also saves a negation in all methods using this. public: + friend FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def); void set(double aa, double bb, double cc, double dd) { @@ -462,9 +461,6 @@ public: }; -FArchive &operator<< (FArchive &arc, secplane_t &plane); - - #include "p_3dfloors.h" struct subsector_t; struct sector_t; @@ -576,8 +572,6 @@ struct extsector_t } XFloor; TArray vertices; - - void Serialize(FArchive &arc); }; struct FTransform @@ -623,7 +617,7 @@ struct secspecial_t } }; -FArchive &operator<< (FArchive &arc, secspecial_t &p); +FSerializer &Serialize(FSerializer &arc, const char *key, secspecial_t &spec, secspecial_t *def); enum class EMoveResult { ok, crushed, pastdest }; @@ -1110,9 +1104,6 @@ public: }; -FArchive &operator<< (FArchive &arc, sector_t::splane &p); - - struct ReverbContainer; struct zone_t { @@ -1282,16 +1273,14 @@ struct side_t }; -FArchive &operator<< (FArchive &arc, side_t::part &p); - struct line_t { vertex_t *v1, *v2; // vertices, from v1 to v2 private: DVector2 delta; // precalculated v2 - v1 for side checking public: - DWORD flags; - DWORD activation; // activation type + uint32_t flags; + uint32_t activation; // activation type int special; int args[5]; // <--- hexen-style arguments (expanded to ZDoom's full width) double alpha; // <--- translucency (0=invisibile, FRACUNIT=opaque) diff --git a/src/r_main.cpp b/src/r_main.cpp index f1d41506ed..ba3c4e846b 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -58,7 +58,6 @@ #include "st_start.h" #include "v_font.h" #include "r_data/colormaps.h" -#include "farchive.h" #include "p_maputl.h" #include "r_swrenderer2.h" diff --git a/src/r_renderer.h b/src/r_renderer.h index a39520b49a..4236993d63 100644 --- a/src/r_renderer.h +++ b/src/r_renderer.h @@ -5,12 +5,13 @@ struct FRenderer; extern FRenderer *Renderer; -class FArchive; +class FSerializer; class FTexture; class AActor; class player_t; struct sector_t; class FCanvasTexture; +class FileWriter; struct FRenderer { @@ -37,7 +38,7 @@ struct FRenderer virtual void RemapVoxels() {} // renders view to a savegame picture - virtual void WriteSavePic (player_t *player, FILE *file, int width, int height) = 0; + virtual void WriteSavePic (player_t *player, FileWriter *file, int width, int height) = 0; // draws player sprites with hardware acceleration (only useful for software rendering) virtual void DrawRemainingPlayerSprites() {} @@ -46,8 +47,8 @@ struct FRenderer virtual void StateChanged(AActor *actor) {} // notify the renderer that serialization of the curent level is about to start/end - virtual void StartSerialize(FArchive &arc) {} - virtual void EndSerialize(FArchive &arc) {} + virtual void StartSerialize(FSerializer &arc) {} + virtual void EndSerialize(FSerializer &arc) {} virtual int GetMaxViewPitch(bool down) = 0; // return value is in plain degrees diff --git a/src/r_state.h b/src/r_state.h index fe3a21060f..b66ad57eb7 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -65,8 +65,7 @@ extern line_t* lines; extern int numsides; extern side_t* sides; -extern int numzones; -extern zone_t* zones; +extern TArray Zones; extern node_t * gamenodes; extern int numgamenodes; diff --git a/src/r_swrenderer.cpp b/src/r_swrenderer.cpp index 9bc8e4b96d..5be8475027 100644 --- a/src/r_swrenderer.cpp +++ b/src/r_swrenderer.cpp @@ -46,7 +46,6 @@ EXTERN_CVAR(Bool, r_shadercolormaps) -class FArchive; void R_SWRSetWindow(int windowSize, int fullWidth, int fullHeight, int stHeight, float trueratio); void R_SetupColormap(player_t *); void R_SetupFreelook(); @@ -204,7 +203,7 @@ void FSoftwareRenderer::RemapVoxels() // //=========================================================================== -void FSoftwareRenderer::WriteSavePic (player_t *player, FILE *file, int width, int height) +void FSoftwareRenderer::WriteSavePic (player_t *player, FileWriter *file, int width, int height) { DCanvas *pic = new DSimpleCanvas (width, height, false); PalEntry palette[256]; diff --git a/src/r_swrenderer.h b/src/r_swrenderer.h index 2856d9586a..f9d5609a0d 100644 --- a/src/r_swrenderer.h +++ b/src/r_swrenderer.h @@ -6,35 +6,35 @@ struct FSoftwareRenderer : public FRenderer { // Can be overridden so that the colormaps for sector color/fade won't be built. - virtual bool UsesColormap() const; + virtual bool UsesColormap() const override; // precache one texture void PrecacheTexture(FTexture *tex, int cache); - virtual void Precache(BYTE *texhitlist, TMap &actorhitlist); + virtual void Precache(BYTE *texhitlist, TMap &actorhitlist) override; // render 3D view - virtual void RenderView(player_t *player); + virtual void RenderView(player_t *player) override; // Remap voxel palette - virtual void RemapVoxels(); + virtual void RemapVoxels() override; // renders view to a savegame picture - virtual void WriteSavePic (player_t *player, FILE *file, int width, int height); + virtual void WriteSavePic (player_t *player, FileWriter *file, int width, int height) override; // draws player sprites with hardware acceleration (only useful for software rendering) - virtual void DrawRemainingPlayerSprites(); + virtual void DrawRemainingPlayerSprites() override; - virtual int GetMaxViewPitch(bool down); + virtual int GetMaxViewPitch(bool down) override; - void OnModeSet (); - void ErrorCleanup (); - void ClearBuffer(int color); - void Init(); - void SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight, float trueratio); - void SetupFrame(player_t *player); - void CopyStackedViewParameters(); - void RenderTextureView (FCanvasTexture *tex, AActor *viewpoint, int fov); - sector_t *FakeFlat(sector_t *sec, sector_t *tempsec, int *floorlightlevel, int *ceilinglightlevel, bool back); + void OnModeSet () override; + void ErrorCleanup () override; + void ClearBuffer(int color) override; + void Init() override; + void SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight, float trueratio) override; + void SetupFrame(player_t *player) override; + void CopyStackedViewParameters() override; + void RenderTextureView (FCanvasTexture *tex, AActor *viewpoint, int fov) override; + sector_t *FakeFlat(sector_t *sec, sector_t *tempsec, int *floorlightlevel, int *ceilinglightlevel, bool back) override; }; diff --git a/src/r_utility.cpp b/src/r_utility.cpp index b80f5c793e..9728128219 100644 --- a/src/r_utility.cpp +++ b/src/r_utility.cpp @@ -53,7 +53,7 @@ #include "v_font.h" #include "r_renderer.h" #include "r_data/colormaps.h" -#include "farchive.h" +#include "serializer.h" #include "r_utility.h" #include "d_player.h" #include "p_local.h" @@ -1041,33 +1041,49 @@ void FCanvasTextureInfo::EmptyList () // //========================================================================== -void FCanvasTextureInfo::Serialize (FArchive &arc) +void FCanvasTextureInfo::Serialize(FSerializer &arc) { - if (arc.IsStoring ()) + if (arc.isWriting()) { - FCanvasTextureInfo *probe; - - for (probe = List; probe != NULL; probe = probe->Next) + if (List != nullptr) { - if (probe->Texture != NULL && probe->Viewpoint != NULL) + if (arc.BeginArray("canvastextures")) { - arc << probe->Viewpoint << probe->FOV << probe->PicNum; + FCanvasTextureInfo *probe; + + for (probe = List; probe != nullptr; probe = probe->Next) + { + if (probe->Texture != nullptr && probe->Viewpoint != nullptr) + { + if (arc.BeginObject(nullptr)) + { + arc("viewpoint", probe->Viewpoint) + ("fov", probe->FOV) + ("texture", probe->PicNum) + .EndObject(); + } + } + } + arc.EndArray(); } } - AActor *nullactor = NULL; - arc << nullactor; } else { - AActor *viewpoint; - int fov; - FTextureID picnum; - - EmptyList (); - while (arc << viewpoint, viewpoint != NULL) + if (arc.BeginArray("canvastextures")) { - arc << fov << picnum; - Add (viewpoint, picnum, fov); + AActor *viewpoint; + int fov; + FTextureID picnum; + while (arc.BeginObject(nullptr)) + { + arc("viewpoint", viewpoint) + ("fov", fov) + ("texture", picnum) + .EndObject(); + Add(viewpoint, picnum, fov); + } + arc.EndArray(); } } } diff --git a/src/r_utility.h b/src/r_utility.h index 09a8b56678..da9a32d117 100644 --- a/src/r_utility.h +++ b/src/r_utility.h @@ -3,6 +3,8 @@ #include "r_state.h" #include "vectors.h" + +class FSerializer; // // Stuff from r_main.h that's needed outside the rendering code. @@ -113,7 +115,7 @@ struct FCanvasTextureInfo static void Add (AActor *viewpoint, FTextureID picnum, int fov); static void UpdateAll (); static void EmptyList (); - static void Serialize (FArchive &arc); + static void Serialize(FSerializer &arc); static void Mark(); private: diff --git a/src/rapidjson/allocators.h b/src/rapidjson/allocators.h new file mode 100644 index 0000000000..98affe03fb --- /dev/null +++ b/src/rapidjson/allocators.h @@ -0,0 +1,271 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return std::malloc(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } + static void Free(void *ptr) { std::free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator()); + if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/src/rapidjson/document.h b/src/rapidjson/document.h new file mode 100644 index 0000000000..19f5a6a5ff --- /dev/null +++ b/src/rapidjson/document.h @@ -0,0 +1,2575 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include // placement new +#include + +RAPIDJSON_DIAG_PUSH +#ifdef _MSC_VER +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#if __GNUC__ >= 6 +RAPIDJSON_DIAG_OFF(terminate) // ignore throwing RAPIDJSON_ASSERT in RAPIDJSON_NOEXCEPT functions +#endif +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::iterator, std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +template +class GenericDocument; + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +struct GenericMember { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator + : public std::iterator >::Type> { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef std::iterator BaseType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + //! Pointer to (const) GenericMember + typedef typename BaseType::pointer Pointer; + //! Reference to (const) GenericMember + typedef typename BaseType::reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef typename BaseType::difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + bool operator==(ConstIterator that) const { return ptr_ == that.ptr_; } + bool operator!=(ConstIterator that) const { return ptr_ != that.ptr_; } + bool operator<=(ConstIterator that) const { return ptr_ <= that.ptr_; } + bool operator>=(ConstIterator that) const { return ptr_ >= that.ptr_; } + bool operator< (ConstIterator that) const { return ptr_ < that.ptr_; } + bool operator> (ConstIterator that) const { return ptr_ > that.ptr_; } + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +struct GenericMemberIterator; + +//! non-const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +struct GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(internal::StrLen(str)){ RAPIDJSON_ASSERT(s != 0); } + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(str), length(len) { RAPIDJSON_ASSERT(s != 0); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; +}; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str, internal::StrLen(str)); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template +struct TypeHelper {}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template +struct TypeHelper { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template +struct TypeHelper > { + typedef std::basic_string StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template +struct TypeHelper { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template +struct TypeHelper { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template class GenericArray; +template class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + typedef GenericArray Array; + typedef GenericArray ConstArray; + typedef GenericObject Object; + typedef GenericObject ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template + GenericValue(GenericDocument&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template + GenericValue& operator=(GenericDocument&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[7] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_ASSERT(type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \see CopyFrom() + */ + template< typename SourceAllocator > + GenericValue(const GenericValue& rhs, Allocator & allocator); + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + RAPIDJSON_ASSERT(this != &rhs); + this->~GenericValue(); + RawAssign(rhs); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator) { + RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Linear time complexity (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast(u); + return (d >= 0.0) + && (d < static_cast(std::numeric_limits::max())) + && (u == static_cast(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast(i); + return (d >= static_cast(std::numeric_limits::min())) + && (d < static_cast(std::numeric_limits::max())) + && (i == static_cast(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast(-std::numeric_limits::max()) + || a > static_cast(std::numeric_limits::max())) + return false; + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) { + if (o.capacity == 0) { + o.capacity = kDefaultObjectCapacity; + SetMembersPointer(reinterpret_cast(allocator.Malloc(o.capacity * sizeof(Member)))); + } + else { + SizeType oldCapacity = o.capacity; + o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5 + SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), oldCapacity * sizeof(Member), o.capacity * sizeof(Member)))); + } + } + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(&*pos, &*last, static_cast(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(pos, last, static_cast(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(f); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(StringRef(s, length), allocator); return *this; } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(s, internal::StrLen(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), SizeType(s.size()), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string + */ + template + bool Is() const { return internal::TypeHelper::Is(*this); } + + template + T Get() const { return internal::TypeHelper::Get(*this); } + + template + T Get() { return internal::TypeHelper::Get(*this); } + + template + ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } + + template + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } + inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(e, values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(m, members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template , typename StackAllocator = CrtAllocator> +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + GenericReader reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(static_cast(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream is(ms); + ParseStream(is); + return *this; + } + + template + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template + GenericDocument& Parse(const std::basic_string& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse(str.c_str()); + } + + template + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + +// defined here due to the dependency on GenericDocument +template +template +inline +GenericValue::GenericValue(const GenericValue& rhs, Allocator& allocator) +{ + switch (rhs.GetType()) { + case kObjectType: + case kArrayType: { // perform deep copy via SAX Handler + GenericDocument d(&allocator); + rhs.Accept(d); + RawAssign(*d.stack_.template Pop(1)); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + } else { + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + } + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + break; + } +} + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericArray { +public: + typedef GenericArray ConstArray; + typedef GenericArray Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericObject { +public: + typedef GenericObject ConstObject; + typedef GenericObject Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + SizeType MemberCount() const { return value_.MemberCount(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template ValueType& operator[](T* name) const { return value_[name]; } + template ValueType& operator[](const GenericValue& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } +#endif + template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { return value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } +#endif + template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/src/rapidjson/encodedstream.h b/src/rapidjson/encodedstream.h new file mode 100644 index 0000000000..145068386a --- /dev/null +++ b/src/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + unsigned pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/rapidjson/encodings.h b/src/rapidjson/encodings.h new file mode 100644 index 0000000000..baa7c2b17f --- /dev/null +++ b/src/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) +#define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast(c); + return true; + } + + unsigned char type = GetRange(static_cast(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFF >> type) & static_cast(c); + } + bool result = true; + switch (type) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define COPY() os.Put(c = is.Take()) +#define TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define TAIL() COPY(); TRANS(0x70) + Ch c; + COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast(c))) { + case 2: TAIL(); return result; + case 3: TAIL(); TAIL(); return result; + case 4: COPY(); TRANS(0x50); TAIL(); return result; + case 5: COPY(); TRANS(0x10); TAIL(); TAIL(); return result; + case 6: TAIL(); TAIL(); TAIL(); return result; + case 10: COPY(); TRANS(0x20); TAIL(); return result; + case 11: COPY(); TRANS(0x60); TAIL(); TAIL(); return result; + default: return false; + } +#undef COPY +#undef TRANS +#undef TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xEFu)); + os.Put(static_cast(0xBBu)); + os.Put(static_cast(0xBFu)); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put((v & 0x3FF) | 0xDC00); + } + } + + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast((v >> 10) | 0xD800)); + PutUnsafe(os, (v & 0x3FF) | 0xDC00); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(static_cast(c) & 0xFFu)); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 8; + c |= static_cast(is.Take()); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + os.Put(static_cast(static_cast(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 24; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 24; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((c >> 24) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast(is.Take()); + os.Put(static_cast(c)); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast(Take(is)); + return static_cast(c); + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + RAPIDJSON_FORCEINLINE static void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + RAPIDJSON_FORCEINLINE static bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + RAPIDJSON_FORCEINLINE static bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/src/rapidjson/error/en.h b/src/rapidjson/error/en.h new file mode 100644 index 0000000000..2db838bff2 --- /dev/null +++ b/src/rapidjson/error/en.h @@ -0,0 +1,74 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/src/rapidjson/error/error.h b/src/rapidjson/error/error.h new file mode 100644 index 0000000000..95cb31a72f --- /dev/null +++ b/src/rapidjson/error/error.h @@ -0,0 +1,155 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Conversion to \c bool, returns \c true, iff !\ref IsError(). + operator bool() const { return !IsError(); } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/src/rapidjson/filereadstream.h b/src/rapidjson/filereadstream.h new file mode 100644 index 0000000000..b56ea13b34 --- /dev/null +++ b/src/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/rapidjson/filewritestream.h b/src/rapidjson/filewritestream.h new file mode 100644 index 0000000000..6378dd60ed --- /dev/null +++ b/src/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + if (result < static_cast(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/src/rapidjson/fwd.h b/src/rapidjson/fwd.h new file mode 100644 index 0000000000..e8104e841b --- /dev/null +++ b/src/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template struct UTF8; +template struct UTF16; +template struct UTF16BE; +template struct UTF16LE; +template struct UTF32; +template struct UTF32BE; +template struct UTF32LE; +template struct ASCII; +template struct AutoUTF; + +template +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template +class MemoryPoolAllocator; + +// stream.h + +template +struct GenericStringStream; + +typedef GenericStringStream > StringStream; + +template +struct GenericInsituStringStream; + +typedef GenericInsituStringStream > InsituStringStream; + +// stringbuffer.h + +template +class GenericStringBuffer; + +typedef GenericStringBuffer, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template +struct BaseReaderHandler; + +template +class GenericReader; + +typedef GenericReader, UTF8, CrtAllocator> Reader; + +// writer.h + +template +class Writer; + +// prettywriter.h + +template +class PrettyWriter; + +// document.h + +template +struct GenericMember; + +template +class GenericMemberIterator; + +template +struct GenericStringRef; + +template +class GenericValue; + +typedef GenericValue, MemoryPoolAllocator > Value; + +template +class GenericDocument; + +typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; + +// pointer.h + +template +class GenericPointer; + +typedef GenericPointer Pointer; + +// schema.h + +template +class IGenericRemoteSchemaDocumentProvider; + +template +class GenericSchemaDocument; + +typedef GenericSchemaDocument SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/src/rapidjson/internal/biginteger.h b/src/rapidjson/internal/biginteger.h new file mode 100644 index 0000000000..9d3e88c998 --- /dev/null +++ b/src/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(&digits_[count_ - 1 + offset], &digits_[count_ - 1], count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/src/rapidjson/internal/diyfp.h b/src/rapidjson/internal/diyfp.h new file mode 100644 index 0000000000..c9fefdc613 --- /dev/null +++ b/src/rapidjson/internal/diyfp.h @@ -0,0 +1,258 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && defined(_M_AMD64) +#include +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - index), e - (63 - index)); +#elif defined(__GNUC__) && __GNUC__ >= 4 + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & (static_cast(1) << 63))) { + res.f <<= 1; + res.e--; + } + return res; +#endif + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + unsigned index = (static_cast(exp) + 348u) / 8u; + *outExp = -348 + static_cast(index) * 8; + return GetCachedPowerByIndex(index); + } + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/src/rapidjson/internal/dtoa.h b/src/rapidjson/internal/dtoa.h new file mode 100644 index 0000000000..8d6350e626 --- /dev/null +++ b/src/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline unsigned CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + unsigned kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -static_cast(kappa); + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[-static_cast(kappa)] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/src/rapidjson/internal/ieee754.h b/src/rapidjson/internal/ieee754.h new file mode 100644 index 0000000000..82bb0b99e5 --- /dev/null +++ b/src/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static unsigned EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return static_cast(order) + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/src/rapidjson/internal/itoa.h b/src/rapidjson/internal/itoa.h new file mode 100644 index 0000000000..01a4e7e72d --- /dev/null +++ b/src/rapidjson/internal/itoa.h @@ -0,0 +1,304 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + if (value >= kTen8) + *buffer++ = cDigitsLut[d4 + 1]; + + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/src/rapidjson/internal/meta.h b/src/rapidjson/internal/meta.h new file mode 100644 index 0000000000..5a9aaa4286 --- /dev/null +++ b/src/rapidjson/internal/meta.h @@ -0,0 +1,181 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif +#if defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(__GNUC__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/src/rapidjson/internal/pow10.h b/src/rapidjson/internal/pow10.h new file mode 100644 index 0000000000..02f475d705 --- /dev/null +++ b/src/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/src/rapidjson/internal/regex.h b/src/rapidjson/internal/regex.h new file mode 100644 index 0000000000..422a5240bf --- /dev/null +++ b/src/rapidjson/internal/regex.h @@ -0,0 +1,701 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef typename Encoding::Ch Ch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + states_(allocator, 256), ranges_(allocator, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + stateSet_(), state0_(allocator, 0), state1_(allocator, 0), anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream > ds(ss); + Parse(ds); + } + + ~GenericRegex() { + Allocator::Free(stateSet_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + + template + bool Match(InputStream& is) const { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) const { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) const { + return SearchWithAnchoring(is, anchorBegin_, anchorEnd_); + } + + bool Search(const Ch* s) const { + GenericStringStream is(s); + return Search(is); + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + template + class DecodedStream { + public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + + private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Allocator allocator; + Stack operandStack(&allocator, 256); // Frag + Stack operatorStack(&allocator, 256); // Operator + Stack atomCountStack(&allocator, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + + // Preallocate buffer for SearchWithAnchoring() + RAPIDJSON_ASSERT(stateSet_ == 0); + if (stateCount_ > 0) { + stateSet_ = static_cast(states_.GetAllocator().Malloc(GetStateSetSize())); + state0_.template Reserve(stateCount_); + state1_.template Reserve(stateCount_); + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + default: + RAPIDJSON_ASSERT(op == kOneOrMore); + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) const { + RAPIDJSON_ASSERT(IsValid()); + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == kAnyCharacterClass || + (sr.codepoint == kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) const { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1 << (index & 31)))) { + stateSet_[index >> 5] |= (1 << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (GetRange(rangeIndex).start & kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = GetRange(rangeIndex); + if (codepoint >= (r.start & ~kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + uint32_t* stateSet_; // allocated by states_.GetAllocator() + mutable Stack state0_; + mutable Stack state1_; + bool anchorBegin_; + bool anchorEnd_; +}; + +typedef GenericRegex > Regex; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/src/rapidjson/internal/stack.h b/src/rapidjson/internal/stack.h new file mode 100644 index 0000000000..022c9aab41 --- /dev/null +++ b/src/rapidjson/internal/stack.h @@ -0,0 +1,230 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(stackTop_ + sizeof(T) * count > stackEnd_)) + Expand(count); + } + + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve(count); + return PushUnsafe(count); + } + + template + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_ + sizeof(T) * count <= stackEnd_); + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + + template + T* Bottom() { return reinterpret_cast(stack_); } + + template + const T* Bottom() const { return reinterpret_cast(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/src/rapidjson/internal/strfunc.h b/src/rapidjson/internal/strfunc.h new file mode 100644 index 0000000000..de41d8f9cc --- /dev/null +++ b/src/rapidjson/internal/strfunc.h @@ -0,0 +1,58 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + RAPIDJSON_ASSERT(s != 0); + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + RAPIDJSON_ASSERT(s != 0); + RAPIDJSON_ASSERT(outCount != 0); + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/src/rapidjson/internal/strtod.h b/src/rapidjson/internal/strtod.h new file mode 100644 index 0000000000..289c413b07 --- /dev/null +++ b/src/rapidjson/internal/strtod.h @@ -0,0 +1,269 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosition, int exp, double* result) { + uint64_t significand = 0; + size_t i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < length; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast(decimals[i] - '0'); + } + + if (i < length && decimals[i] >= '5') // Rounding + significand++; + + size_t remaining = length - i; + const unsigned kUlpShift = 3; + const unsigned kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + const int dExp = static_cast(decimalPosition) - static_cast(i) + exp; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp - 1; + RAPIDJSON_ASSERT(adjustment >= 0 && adjustment < 7); + v = v * kPow10[adjustment]; + if (length + static_cast(adjustment)> 19u) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const unsigned effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + unsigned precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + unsigned scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + static_cast(kUlp); + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + static_cast(precisionSize)); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, size_t length, size_t decimalPosition, int exp) { + const BigInteger dInt(decimals, length); + const int dExp = static_cast(decimalPosition) - static_cast(length) + exp; + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result; + if (StrtodFast(d, p, &result)) + return result; + + // Trim leading zeros + while (*decimals == '0' && length > 1) { + length--; + decimals++; + decimalPosition--; + } + + // Trim trailing zeros + while (decimals[length - 1] == '0' && length > 1) { + length--; + decimalPosition--; + exp++; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 780; + if (static_cast(length) > kMaxDecimalDigit) { + int delta = (static_cast(length) - kMaxDecimalDigit); + exp += delta; + decimalPosition -= static_cast(delta); + length = kMaxDecimalDigit; + } + + // If too small, underflow to zero + if (int(length) + exp < -324) + return 0.0; + + if (StrtodDiyFp(decimals, length, decimalPosition, exp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, length, decimalPosition, exp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/src/rapidjson/internal/swap.h b/src/rapidjson/internal/swap.h new file mode 100644 index 0000000000..666e49f97b --- /dev/null +++ b/src/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/src/rapidjson/istreamwrapper.h b/src/rapidjson/istreamwrapper.h new file mode 100644 index 0000000000..f5fe28977e --- /dev/null +++ b/src/rapidjson/istreamwrapper.h @@ -0,0 +1,115 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicIStreamWrapper(StreamType& stream) : stream_(stream), count_(), peekBuffer_() {} + + Ch Peek() const { + typename StreamType::int_type c = stream_.peek(); + return RAPIDJSON_LIKELY(c != StreamType::traits_type::eof()) ? static_cast(c) : '\0'; + } + + Ch Take() { + typename StreamType::int_type c = stream_.get(); + if (RAPIDJSON_LIKELY(c != StreamType::traits_type::eof())) { + count_++; + return static_cast(c); + } + else + return '\0'; + } + + // tellg() may return -1 when failed. So we count by ourself. + size_t Tell() const { return count_; } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + RAPIDJSON_ASSERT(sizeof(Ch) == 1); // Only usable for byte stream. + int i; + bool hasError = false; + for (i = 0; i < 4; ++i) { + typename StreamType::int_type c = stream_.get(); + if (c == StreamType::traits_type::eof()) { + hasError = true; + stream_.clear(); + break; + } + peekBuffer_[i] = static_cast(c); + } + for (--i; i >= 0; --i) + stream_.putback(peekBuffer_[i]); + return !hasError ? peekBuffer_ : 0; + } + +private: + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + StreamType& stream_; + size_t count_; //!< Number of characters read. Note: + mutable Ch peekBuffer_[4]; +}; + +typedef BasicIStreamWrapper IStreamWrapper; +typedef BasicIStreamWrapper WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/src/rapidjson/memorybuffer.h b/src/rapidjson/memorybuffer.h new file mode 100644 index 0000000000..39bee1dec1 --- /dev/null +++ b/src/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/src/rapidjson/memorystream.h b/src/rapidjson/memorystream.h new file mode 100644 index 0000000000..1d71d8a4f0 --- /dev/null +++ b/src/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/src/rapidjson/msinttypes/inttypes.h b/src/rapidjson/msinttypes/inttypes.h new file mode 100644 index 0000000000..18111286bf --- /dev/null +++ b/src/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/src/rapidjson/msinttypes/stdint.h b/src/rapidjson/msinttypes/stdint.h new file mode 100644 index 0000000000..3d4477b9a0 --- /dev/null +++ b/src/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/src/rapidjson/ostreamwrapper.h b/src/rapidjson/ostreamwrapper.h new file mode 100644 index 0000000000..6f4667c08a --- /dev/null +++ b/src/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper OStreamWrapper; +typedef BasicOStreamWrapper WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/src/rapidjson/pointer.h b/src/rapidjson/pointer.h new file mode 100644 index 0000000000..0206ac1c8b --- /dev/null +++ b/src/rapidjson/pointer.h @@ -0,0 +1,1358 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = buffer[i]; + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(hexDigits[u >> 4]); + os_.Put(hexDigits[u & 15]); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/src/rapidjson/prettywriter.h b/src/rapidjson/prettywriter.h new file mode 100644 index 0000000000..c6f0216e98 --- /dev/null +++ b/src/rapidjson/prettywriter.h @@ -0,0 +1,261 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of ouptut os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::WriteNull(); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::WriteBool(b); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::WriteInt(i); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::WriteUint(u); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::WriteInt64(i64); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::WriteUint64(u64); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::WriteDouble(d); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kNumberType); + return Base::WriteString(str, length); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kStringType); + return Base::WriteString(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndObject(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::WriteEndArray(); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::os_->Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + PrettyPrefix(type); + return Base::WriteRawValue(json, length); + } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/rapidjson/rapidjson.h b/src/rapidjson/rapidjson.h new file mode 100644 index 0000000000..053b2ce43f --- /dev/null +++ b/src/rapidjson/rapidjson.h @@ -0,0 +1,615 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overriden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && defined(_M_ARM) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianess detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. Currently the default uses 4 bytes + alignment on 32-bit platforms and 8 bytes alignment for 64-bit platforms. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#if RAPIDJSON_64BIT == 1 +#define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) +#else +#define RAPIDJSON_ALIGN(x) (((x) + 3u) & ~3u) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2 optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2 or SSE4.2 SIMD extensions on modern Intel-compatible + processors. + + To enable these optimizations, two different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + \c RAPIDJSON_SSE42 takes precedence, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Adopt from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y + +#if defined(__GNUC__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) +// (defined(_MSC_VER) && _MSC_VER >= ????) // not yet supported +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +//!@endcond + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(x) new x +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/rapidjson/reader.h b/src/rapidjson/reader.h new file mode 100644 index 0000000000..19f8849b14 --- /dev/null +++ b/src/rapidjson/reader.h @@ -0,0 +1,1879 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool Int(int) { return static_cast(*this).Default(); } + bool Uint(unsigned) { return static_cast(*this).Default(); } + bool Int64(int64_t) { return static_cast(*this).Default(); } + bool Uint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const int r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r != 0) { // some of characters is non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + const int r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r != 0) { // some of characters is non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_SSE2 + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(stackAllocator, stackCapacity), parseResult_() {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Whether a parse error has occured in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n'); + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the inital '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { + is.Take(); + os.Put(static_cast(escape[static_cast(e)])); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast(__builtin_ffs(r) - 1); + #endif + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif + + template + class NumberStream; + + template + class NumberStream { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + ~NumberStream() {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + useNanOrInf = true; + if (RAPIDJSON_LIKELY(Consume(s, 'N') && Consume(s, 'a') && Consume(s, 'N'))) { + d = std::numeric_limits::quiet_NaN(); + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(d >= 1.7976931348623157e307)) // DBL_MAX / 10.0 + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast(s.Take() - '0'); + if (expMinus) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (exp >= 214748364) { // Issue #313: prevent overflow exponent + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast(s.Length()); + StringStream srcStream(s.Pop()); + StackStream dstStream(stack_); + while (numCharsToCopy--) { + Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : + ParseNumber(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingStartState = 0, + IterativeParsingFinishState, + IterativeParsingErrorState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingKeyValueDelimiterState, + IterativeParsingMemberValueState, + IterativeParsingMemberDelimiterState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingElementDelimiterState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState + }; + + enum { cIterativeParsingStateCount = IterativeParsingValueState + 1 }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast(c) < 256) + return static_cast(tokenMap[static_cast(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + } + }; // End of G + + return static_cast(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/src/rapidjson/schema.h b/src/rapidjson/schema.h new file mode 100644 index 0000000000..8497d30315 --- /dev/null +++ b/src/rapidjson/schema.h @@ -0,0 +1,2006 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, const SchemaType* s) : + factory(f), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false) + { + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256 + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = GetTypeless(); + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + } + + ~Schema() { + if (allocator_) { + allocator_->Free(enum_); + } + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + allocator_->Free(pattern_); + } +#endif + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = GetTypeless(); + else + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + else + context.valueSchema = GetTypeless(); + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (!patternValid && !otherValid) // kPatternValidatorWithAdditionalProperty) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + else + oneValid = true; + } + if (!oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + if (count > maxLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + } + + SizeType index; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = GetTypeless(); + return true; + } + + if (context.patternPropertiesSchemaCount == 0) // patternProperties are not additional properties + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required) + if (!context.propertyExist[index]) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + + if (memberCount < minProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + + if (memberCount > maxProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + + if (hasDependencies_) { + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) + if (context.propertyExist[sourceIndex]) { + if (properties_[sourceIndex].dependencies) { + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (properties_[sourceIndex].dependencies[targetIndex] && !context.propertyExist[targetIndex]) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + else if (properties_[sourceIndex].dependenciesSchema) + if (!context.validators[properties_[sourceIndex].dependenciesValidatorIndex]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + + if (elementCount > maxItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, sizeof(s) / sizeof(Ch) - 1);\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + static const SchemaType* GetTypeless() { + static SchemaType typeless(0, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), 0); + return &typeless; + } + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString()); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + return pattern->Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) + try { + return new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (minimum_.IsUint64()) { + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (maximum_.IsUint64()) + /* do nothing */; // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (maximum_.IsInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + return true; + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = buffer[i]; + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = SchemaType::GetTypeless(); + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(outputHandler), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : schemaDocument_->GetPointer(&CurrentSchema()); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + return documentStack_.Empty() ? PointerType() : PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && outputHandler_.method arg2 + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext() ), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = outputHandler_.StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = outputHandler_.Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = outputHandler_.StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + return StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator()); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + if (CurrentContext().valueSchema) + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static OutputHandler& GetNullHandler() { + static OutputHandler nullHandler; + return nullHandler; + } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + OutputHandler& outputHandler_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/src/rapidjson/stream.h b/src/rapidjson/stream.h new file mode 100644 index 0000000000..fef82c252f --- /dev/null +++ b/src/rapidjson/stream.h @@ -0,0 +1,179 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/src/rapidjson/stringbuffer.h b/src/rapidjson/stringbuffer.h new file mode 100644 index 0000000000..78f34d2098 --- /dev/null +++ b/src/rapidjson/stringbuffer.h @@ -0,0 +1,117 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + + void Reserve(size_t count) { stack_.template Reserve(count); } + Ch* Push(size_t count) { return stack_.template Push(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +template +inline void PutReserve(GenericStringBuffer& stream, size_t count) { + stream.Reserve(count); +} + +template +inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/src/rapidjson/writer.h b/src/rapidjson/writer.h new file mode 100644 index 0000000000..c5a3b98a92 --- /dev/null +++ b/src/rapidjson/writer.h @@ -0,0 +1,616 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + Prefix(type); + return EndValue(WriteRawValue(json, length)); + } + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + static const size_t kDefaultLevelDepth = 32; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename TargetEncoding::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast(escape[static_cast(c)])); + if (escape[static_cast(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + for (size_t i = 0; i < length; i++) { + RAPIDJSON_ASSERT(json[i] != '\0'); + PutUnsafe(*os_, json[i]); + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + os_->Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19 }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x19) == 0x19 + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + +RAPIDJSON_NAMESPACE_END + +#ifdef _MSC_VER +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/src/resourcefiles/file_zip.cpp b/src/resourcefiles/file_zip.cpp index 0506a0f3f6..73193062c3 100644 --- a/src/resourcefiles/file_zip.cpp +++ b/src/resourcefiles/file_zip.cpp @@ -33,7 +33,8 @@ ** */ -#include "resourcefile.h" +#include +#include "file_zip.h" #include "cmdlib.h" #include "templates.h" #include "v_text.h" @@ -44,6 +45,69 @@ #define BUFREADCOMMENT (0x400) +//========================================================================== +// +// Decompression subroutine +// +//========================================================================== + +static bool UncompressZipLump(char *Cache, FileReader *Reader, int Method, int LumpSize, int CompressedSize, int GPFlags) +{ + switch (Method) + { + case METHOD_STORED: + { + Reader->Read(Cache, LumpSize); + break; + } + + case METHOD_DEFLATE: + { + FileReaderZ frz(*Reader, true); + frz.Read(Cache, LumpSize); + break; + } + + case METHOD_BZIP2: + { + FileReaderBZ2 frz(*Reader); + frz.Read(Cache, LumpSize); + break; + } + + case METHOD_LZMA: + { + FileReaderLZMA frz(*Reader, LumpSize, true); + frz.Read(Cache, LumpSize); + break; + } + + case METHOD_IMPLODE: + { + FZipExploder exploder; + exploder.Explode((unsigned char *)Cache, LumpSize, Reader, CompressedSize, GPFlags); + break; + } + + case METHOD_SHRINK: + { + ShrinkLoop((unsigned char *)Cache, LumpSize, Reader, CompressedSize); + break; + } + + default: + assert(0); + return false; + } + return true; +} + +bool FCompressedBuffer::Decompress(char *destbuffer) +{ + MemoryReader mr(mBuffer, mCompressedSize); + return UncompressZipLump(destbuffer, &mr, mMethod, mSize, mCompressedSize, mZipFlags); +} + //----------------------------------------------------------------------- // // Finds the central directory end record in the end of the file. @@ -96,56 +160,6 @@ static DWORD Zip_FindCentralDir(FileReader * fin) return uPosFound; } - -enum -{ - LUMPFZIP_NEEDFILESTART = 128 -}; - -//========================================================================== -// -// Zip Lump -// -//========================================================================== - -struct FZipLump : public FResourceLump -{ - WORD GPFlags; - BYTE Method; - int CompressedSize; - int Position; - - virtual FileReader *GetReader(); - virtual int FillCache(); - -private: - void SetLumpAddress(); - virtual int GetFileOffset() - { - if (Method != METHOD_STORED) return -1; - if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress(); return Position; - } -}; - - -//========================================================================== -// -// Zip file -// -//========================================================================== - -class FZipFile : public FResourceFile -{ - FZipLump *Lumps; - -public: - FZipFile(const char * filename, FileReader *file); - virtual ~FZipFile(); - bool Open(bool quiet); - virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; } -}; - - //========================================================================== // // Zip file @@ -252,6 +266,7 @@ bool FZipFile::Open(bool quiet) lump_p->Flags = LUMPF_ZIPFILE | LUMPFZIP_NEEDFILESTART; lump_p->Method = BYTE(zip_fh->Method); lump_p->GPFlags = zip_fh->Flags; + lump_p->CRC32 = zip_fh->CRC32; lump_p->CompressedSize = LittleLong(zip_fh->CompressedSize); lump_p->Position = LittleLong(zip_fh->LocalHeaderOffset); lump_p->CheckEmbedded(); @@ -285,6 +300,21 @@ FZipFile::~FZipFile() if (Lumps != NULL) delete [] Lumps; } +//========================================================================== +// +// +// +//========================================================================== + +FCompressedBuffer FZipLump::GetRawData() +{ + FCompressedBuffer cbuf = { (unsigned)LumpSize, (unsigned)CompressedSize, Method, GPFlags, CRC32, new char[CompressedSize] }; + if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress(); + Owner->Reader->Seek(Position, SEEK_SET); + Owner->Reader->Read(cbuf.mBuffer, CompressedSize); + return cbuf; +} + //========================================================================== // // SetLumpAddress @@ -348,56 +378,22 @@ int FZipLump::FillCache() Owner->Reader->Seek(Position, SEEK_SET); Cache = new char[LumpSize]; - switch (Method) - { - case METHOD_STORED: - { - Owner->Reader->Read(Cache, LumpSize); - break; - } - - case METHOD_DEFLATE: - { - FileReaderZ frz(*Owner->Reader, true); - frz.Read(Cache, LumpSize); - break; - } - - case METHOD_BZIP2: - { - FileReaderBZ2 frz(*Owner->Reader); - frz.Read(Cache, LumpSize); - break; - } - - case METHOD_LZMA: - { - FileReaderLZMA frz(*Owner->Reader, LumpSize, true); - frz.Read(Cache, LumpSize); - break; - } - - case METHOD_IMPLODE: - { - FZipExploder exploder; - exploder.Explode((unsigned char *)Cache, LumpSize, Owner->Reader, CompressedSize, GPFlags); - break; - } - - case METHOD_SHRINK: - { - ShrinkLoop((unsigned char *)Cache, LumpSize, Owner->Reader, CompressedSize); - break; - } - - default: - assert(0); - return 0; - } + UncompressZipLump(Cache, Owner->Reader, Method, LumpSize, CompressedSize, GPFlags); RefCount = 1; return 1; } +//========================================================================== +// +// +// +//========================================================================== + +int FZipLump::GetFileOffset() +{ + if (Method != METHOD_STORED) return -1; + if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress(); return Position; +} //========================================================================== // @@ -428,3 +424,169 @@ FResourceFile *CheckZip(const char *filename, FileReader *file, bool quiet) +//========================================================================== +// +// time_to_dos +// +// Converts time from struct tm to the DOS format used by zip files. +// +//========================================================================== + +static void time_to_dos(struct tm *time, unsigned short *dosdate, unsigned short *dostime) +{ + if (time == NULL || time->tm_year < 80) + { + *dosdate = *dostime = 0; + } + else + { + *dosdate = LittleShort((time->tm_year - 80) * 512 + (time->tm_mon + 1) * 32 + time->tm_mday); + *dostime = LittleShort(time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2); + } +} + +//========================================================================== +// +// append_to_zip +// +// Write a given file to the zipFile. +// +// zipfile: zip object to be written to +// +// returns: position = success, -1 = error +// +//========================================================================== + +int AppendToZip(FILE *zip_file, const char *filename, FCompressedBuffer &content, uint16_t date, uint16_t time) +{ + FZipLocalFileHeader local; + int position; + + local.Magic = ZIP_LOCALFILE; + local.VersionToExtract[0] = 20; + local.VersionToExtract[1] = 0; + local.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort(2) : LittleShort((uint16_t)content.mZipFlags); + local.Method = LittleShort(content.mMethod); + local.ModDate = date; + local.ModTime = time; + local.CRC32 = content.mCRC32; + local.UncompressedSize = LittleLong(content.mSize); + local.CompressedSize = LittleLong(content.mCompressedSize); + local.NameLength = LittleShort((unsigned short)strlen(filename)); + local.ExtraLength = 0; + + // Fill in local directory header. + + position = (int)ftell(zip_file); + + // Write out the header, file name, and file data. + if (fwrite(&local, sizeof(local), 1, zip_file) != 1 || + fwrite(filename, strlen(filename), 1, zip_file) != 1 || + fwrite(content.mBuffer, 1, content.mCompressedSize, zip_file) != content.mCompressedSize) + { + return -1; + } + return position; +} + + +//========================================================================== +// +// write_central_dir +// +// Writes the central directory entry for a file. +// +//========================================================================== + +int AppendCentralDirectory(FILE *zip_file, const char *filename, FCompressedBuffer &content, uint16_t date, uint16_t time, int position) +{ + FZipCentralDirectoryInfo dir; + + dir.Magic = ZIP_CENTRALFILE; + dir.VersionMadeBy[0] = 20; + dir.VersionMadeBy[1] = 0; + dir.VersionToExtract[0] = 20; + dir.VersionToExtract[1] = 0; + dir.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort(2) : LittleShort((uint16_t)content.mZipFlags); + dir.Method = LittleShort(content.mMethod); + dir.ModTime = time; + dir.ModDate = date; + dir.CRC32 = content.mCRC32; + dir.CompressedSize = LittleLong(content.mCompressedSize); + dir.UncompressedSize = LittleLong(content.mSize); + dir.NameLength = LittleShort((unsigned short)strlen(filename)); + dir.ExtraLength = 0; + dir.CommentLength = 0; + dir.StartingDiskNumber = 0; + dir.InternalAttributes = 0; + dir.ExternalAttributes = 0; + dir.LocalHeaderOffset = LittleLong(position); + + if (fwrite(&dir, sizeof(dir), 1, zip_file) != 1 || + fwrite(filename, strlen(filename), 1, zip_file) != 1) + { + return -1; + } + return 0; +} + +bool WriteZip(const char *filename, TArray &filenames, TArray &content) +{ + // try to determine local time + struct tm *ltime; + time_t ttime; + uint16_t mydate, mytime; + ttime = time(nullptr); + ltime = localtime(&ttime); + time_to_dos(ltime, &mydate, &mytime); + + TArray positions; + + if (filenames.Size() != content.Size()) return false; + + FILE *f = fopen(filename, "wb"); + if (f != nullptr) + { + for (unsigned i = 0; i < filenames.Size(); i++) + { + int pos = AppendToZip(f, filenames[i], content[i], mydate, mytime); + if (pos == -1) + { + fclose(f); + remove(filename); + return false; + } + positions.Push(pos); + } + + int dirofs = (int)ftell(f); + for (unsigned i = 0; i < filenames.Size(); i++) + { + if (AppendCentralDirectory(f, filenames[i], content[i], mydate, mytime, positions[i]) < 0) + { + fclose(f); + remove(filename); + return false; + } + } + + // Write the directory terminator. + FZipEndOfCentralDirectory dirend; + dirend.Magic = ZIP_ENDOFDIR; + dirend.DiskNumber = 0; + dirend.FirstDisk = 0; + dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort(filenames.Size()); + dirend.DirectoryOffset = dirofs; + dirend.DirectorySize = LittleLong(ftell(f) - dirofs); + dirend.ZipCommentLength = 0; + if (fwrite(&dirend, sizeof(dirend), 1, f) != 1) + { + fclose(f); + remove(filename); + return false; + } + fclose(f); + return true; + } + return false; +} diff --git a/src/resourcefiles/file_zip.h b/src/resourcefiles/file_zip.h new file mode 100644 index 0000000000..427d9cbf3b --- /dev/null +++ b/src/resourcefiles/file_zip.h @@ -0,0 +1,53 @@ +#ifndef __FILE_ZIP_H +#define __FILE_ZIP_H + +#include "resourcefile.h" + +enum +{ + LUMPFZIP_NEEDFILESTART = 128 +}; + +//========================================================================== +// +// Zip Lump +// +//========================================================================== + +struct FZipLump : public FResourceLump +{ + WORD GPFlags; + BYTE Method; + int CompressedSize; + int Position; + unsigned CRC32; + + virtual FileReader *GetReader(); + virtual int FillCache(); + +private: + void SetLumpAddress(); + virtual int GetFileOffset(); + FCompressedBuffer GetRawData(); +}; + + +//========================================================================== +// +// Zip file +// +//========================================================================== + +class FZipFile : public FResourceFile +{ + FZipLump *Lumps; + +public: + FZipFile(const char * filename, FileReader *file); + virtual ~FZipFile(); + bool Open(bool quiet); + virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; } +}; + + +#endif \ No newline at end of file diff --git a/src/resourcefiles/resourcefile.cpp b/src/resourcefiles/resourcefile.cpp index c66430a66a..912998d9ae 100644 --- a/src/resourcefiles/resourcefile.cpp +++ b/src/resourcefiles/resourcefile.cpp @@ -40,6 +40,7 @@ #include "doomerrors.h" #include "gi.h" #include "doomstat.h" +#include "w_zip.h" //========================================================================== @@ -191,6 +192,23 @@ void FResourceLump::CheckEmbedded() } +//========================================================================== +// +// this is just for completeness. For non-Zips only an uncompressed lump can +// be returned. +// +//========================================================================== + +FCompressedBuffer FResourceLump::GetRawData() +{ + FCompressedBuffer cbuf = { (unsigned)LumpSize, (unsigned)LumpSize, METHOD_STORED, 0, 0, new char[LumpSize] }; + memcpy(cbuf.mBuffer, CacheLump(), LumpSize); + cbuf.mCRC32 = crc32(0, (BYTE*)cbuf.mBuffer, LumpSize); + ReleaseCache(); + return cbuf; +} + + //========================================================================== // // Returns the owner's FileReader if it can be used to access this lump @@ -270,7 +288,7 @@ FResourceFile *CheckDir(const char *filename, FileReader *file, bool quiet); static CheckFunc funcs[] = { CheckWad, CheckZip, Check7Z, CheckPak, CheckGRP, CheckRFF, CheckLump }; -FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader *file, bool quiet) +FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader *file, bool quiet, bool containeronly) { if (file == NULL) { @@ -283,7 +301,7 @@ FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader return NULL; } } - for(size_t i = 0; i < countof(funcs); i++) + for(size_t i = 0; i < countof(funcs) - containeronly; i++) { FResourceFile *resfile = funcs[i](filename, file, quiet); if (resfile != NULL) return resfile; @@ -560,6 +578,24 @@ void FResourceFile::FindStrifeTeaserVoices () { } +//========================================================================== +// +// Finds a lump by a given name. Used for savegames +// +//========================================================================== + +FResourceLump *FResourceFile::FindLump(const char *name) +{ + for (unsigned i = 0; i < NumLumps; i++) + { + FResourceLump *lump = GetLump(i); + if (!stricmp(name, lump->FullName)) + { + return lump; + } + } + return nullptr; +} //========================================================================== // diff --git a/src/resourcefiles/resourcefile.h b/src/resourcefiles/resourcefile.h index b96075f5d3..bedcad3bb0 100644 --- a/src/resourcefiles/resourcefile.h +++ b/src/resourcefiles/resourcefile.h @@ -8,6 +8,28 @@ class FResourceFile; class FTexture; +// This holds a compresed Zip entry with all needed info to decompress it. +struct FCompressedBuffer +{ + unsigned mSize; + unsigned mCompressedSize; + int mMethod; + int mZipFlags; + unsigned mCRC32; + char *mBuffer; + + bool Decompress(char *destbuffer); + void Clean() + { + mSize = mCompressedSize = 0; + if (mBuffer != nullptr) + { + delete[] mBuffer; + mBuffer = nullptr; + } + } +}; + struct FResourceLump { friend class FResourceFile; @@ -46,6 +68,7 @@ struct FResourceLump virtual int GetIndexNum() const { return 0; } void LumpNameSetup(FString iname); void CheckEmbedded(); + virtual FCompressedBuffer GetRawData(); void *CacheLump(); int ReleaseCache(); @@ -77,7 +100,7 @@ private: void JunkLeftoverFilters(void *lumps, size_t lumpsize, DWORD max); public: - static FResourceFile *OpenResourceFile(const char *filename, FileReader *file, bool quiet = false); + static FResourceFile *OpenResourceFile(const char *filename, FileReader *file, bool quiet = false, bool containeronly = false); static FResourceFile *OpenDirectory(const char *filename, bool quiet = false); virtual ~FResourceFile(); FileReader *GetReader() const { return Reader; } @@ -88,6 +111,7 @@ public: virtual void FindStrifeTeaserVoices (); virtual bool Open(bool quiet) = 0; virtual FResourceLump *GetLump(int no) = 0; + FResourceLump *FindLump(const char *name); }; struct FUncompressedLump : public FResourceLump diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index 8bc1b4868d..b073070fd0 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -50,7 +50,7 @@ #include "d_netinf.h" #include "i_system.h" #include "d_player.h" -#include "farchive.h" +#include "serializer.h" // MACROS ------------------------------------------------------------------ @@ -2102,7 +2102,8 @@ class AAmbientSound : public AActor { DECLARE_CLASS (AAmbientSound, AActor) public: - void Serialize (FArchive &arc); + + void Serialize(FSerializer &arc); void MarkPrecacheSounds () const; void BeginPlay (); @@ -2125,10 +2126,11 @@ IMPLEMENT_CLASS (AAmbientSound) // //========================================================================== -void AAmbientSound::Serialize (FArchive &arc) +void AAmbientSound::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << bActive << NextCheck; + arc("active", bActive) + ("nextcheck", NextCheck); } //========================================================================== @@ -2174,7 +2176,7 @@ void AAmbientSound::Tick () loop = CHAN_LOOP; } - if (ambient->sound != 0) + if (ambient->sound != FSoundID(0)) { // The second argument scales the ambient sound's volume. // 0 and 100 are normal volume. The maximum volume level diff --git a/src/s_environment.cpp b/src/s_environment.cpp index d3bd8c58c7..377537a6c9 100644 --- a/src/s_environment.cpp +++ b/src/s_environment.cpp @@ -1,7 +1,6 @@ #include "doomtype.h" #include "tarray.h" #include "s_sound.h" -#include "farchive.h" #include "sc_man.h" #include "cmdlib.h" #include "templates.h" @@ -481,30 +480,6 @@ void S_AddEnvironment (ReverbContainer *settings) } } -FArchive &operator<< (FArchive &arc, ReverbContainer *&env) -{ - WORD id; - - if (arc.IsStoring()) - { - if (env != NULL) - { - arc << env->ID; - } - else - { - id = 0; - arc << id; - } - } - else - { - arc << id; - env = S_FindEnvironment (id); - } - return arc; -} - static void ReadReverbDef (int lump) { FScanner sc; diff --git a/src/s_sndseq.cpp b/src/s_sndseq.cpp index 06d5883b66..23837c28b8 100644 --- a/src/s_sndseq.cpp +++ b/src/s_sndseq.cpp @@ -27,7 +27,7 @@ #include "templates.h" #include "c_dispatch.h" #include "g_level.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" // MACROS ------------------------------------------------------------------ @@ -106,7 +106,7 @@ class DSeqActorNode : public DSeqNode public: DSeqActorNode(AActor *actor, int sequence, int modenum); void Destroy(); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void MakeSound(int loop, FSoundID id) { S_Sound(m_Actor, CHAN_BODY|loop, id, clamp(m_Volume, 0.f, 1.f), m_Atten); @@ -134,7 +134,7 @@ class DSeqPolyNode : public DSeqNode public: DSeqPolyNode(FPolyObj *poly, int sequence, int modenum); void Destroy(); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void MakeSound(int loop, FSoundID id) { S_Sound (m_Poly, CHAN_BODY|loop, id, clamp(m_Volume, 0.f, 1.f), m_Atten); @@ -162,7 +162,7 @@ class DSeqSectorNode : public DSeqNode public: DSeqSectorNode(sector_t *sec, int chan, int sequence, int modenum); void Destroy(); - void Serialize(FArchive &arc); + void Serialize(FSerializer &arc); void MakeSound(int loop, FSoundID id) { Channel = (Channel & 7) | CHAN_AREA | loop; @@ -280,9 +280,9 @@ static FRandom pr_sndseq ("SndSeq"); // CODE -------------------------------------------------------------------- -void DSeqNode::SerializeSequences (FArchive &arc) +void DSeqNode::SerializeSequences (FSerializer &arc) { - arc << SequenceListHead; + arc("sndseqlisthead", SequenceListHead); } IMPLEMENT_POINTY_CLASS (DSeqNode) @@ -298,55 +298,58 @@ DSeqNode::DSeqNode () m_Next = m_Prev = m_ChildSeqNode = m_ParentSeqNode = NULL; } -void DSeqNode::Serialize (FArchive &arc) +void DSeqNode::Serialize(FSerializer &arc) { int seqOffset; unsigned int i; + FName seqName; + int delayTics = 0; + FSoundID id; + float volume; + float atten = ATTN_NORM; + int seqnum; + unsigned int numchoices; - Super::Serialize (arc); - if (arc.IsStoring ()) + // copy these to local variables so that the actual serialization code does not need to be duplicated for saving and loading. + if (arc.isWriting()) { - seqOffset = (int)SN_GetSequenceOffset (m_Sequence, m_SequencePtr); - arc << seqOffset - << m_DelayUntilTic - << m_Volume - << m_Atten - << m_ModeNum - << m_Next - << m_Prev - << m_ChildSeqNode - << m_ParentSeqNode - << m_CurrentSoundID - << Sequences[m_Sequence]->SeqName; + seqOffset = (int)SN_GetSequenceOffset(m_Sequence, m_SequencePtr); + delayTics = m_DelayUntilTic; + volume = m_Volume; + atten = m_Atten; + id = m_CurrentSoundID; + seqName = Sequences[m_Sequence]->SeqName; + numchoices = m_SequenceChoices.Size(); + } + Super::Serialize(arc); - arc.WriteCount (m_SequenceChoices.Size()); - for (i = 0; i < m_SequenceChoices.Size(); ++i) + arc("seqoffset", seqOffset) + ("delaytics", delayTics) + ("volume", volume) + ("atten", atten) + ("modelnum", m_ModeNum) + ("next", m_Next) + ("prev", m_Prev) + ("childseqnode", m_ChildSeqNode) + ("parentseqnode", m_ParentSeqNode) + ("id", id) + ("seqname", seqName) + ("numchoices", numchoices); + + // The way this is saved makes it hard to encapsulate so just do it the hard way... + if (arc.isWriting()) + { + if (numchoices > 0 && arc.BeginArray("choices")) { - arc << Sequences[m_SequenceChoices[i]]->SeqName; + for (i = 0; i < m_SequenceChoices.Size(); ++i) + { + arc(nullptr, Sequences[m_SequenceChoices[i]]->SeqName); + } + arc.EndArray(); } } else { - FName seqName; - int delayTics = 0; - FSoundID id; - float volume; - float atten = ATTN_NORM; - int seqnum; - unsigned int numchoices; - - arc << seqOffset - << delayTics - << volume - << atten - << m_ModeNum - << m_Next - << m_Prev - << m_ChildSeqNode - << m_ParentSeqNode - << id - << seqName; - seqnum = FindSequence (seqName); if (seqnum >= 0) { @@ -360,12 +363,15 @@ void DSeqNode::Serialize (FArchive &arc) ChangeData (seqOffset, delayTics - TIME_REFERENCE, volume, id); - numchoices = arc.ReadCount(); m_SequenceChoices.Resize(numchoices); - for (i = 0; i < numchoices; ++i) + if (numchoices > 0 && arc.BeginArray("choices")) { - arc << seqName; - m_SequenceChoices[i] = FindSequence (seqName); + for (i = 0; i < numchoices; ++i) + { + arc(nullptr, seqName); + m_SequenceChoices[i] = FindSequence(seqName); + } + arc.EndArray(); } } } @@ -425,26 +431,27 @@ IMPLEMENT_POINTY_CLASS (DSeqActorNode) DECLARE_POINTER (m_Actor) END_POINTERS -void DSeqActorNode::Serialize (FArchive &arc) +void DSeqActorNode::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Actor; + arc("actor", m_Actor); } IMPLEMENT_CLASS (DSeqPolyNode) -void DSeqPolyNode::Serialize (FArchive &arc) +void DSeqPolyNode::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Poly; + //arc << m_Poly; } IMPLEMENT_CLASS (DSeqSectorNode) -void DSeqSectorNode::Serialize (FArchive &arc) +void DSeqSectorNode::Serialize(FSerializer &arc) { Super::Serialize (arc); - arc << m_Sector << Channel; + arc("sector",m_Sector) + ("channel", Channel); } //========================================================================== diff --git a/src/s_sndseq.h b/src/s_sndseq.h index d27dc86daa..74ded5af47 100644 --- a/src/s_sndseq.h +++ b/src/s_sndseq.h @@ -20,7 +20,7 @@ class DSeqNode : public DObject DECLARE_CLASS (DSeqNode, DObject) HAS_OBJECT_POINTERS public: - void Serialize (FArchive &arc); + void Serialize(FSerializer &arc); void StopAndDestroy (); void Destroy (); void Tick (); @@ -38,7 +38,7 @@ public: inline static DSeqNode *FirstSequence() { return SequenceListHead; } inline DSeqNode *NextSequence() const { return m_Next; } - static void SerializeSequences (FArchive &arc); + static void SerializeSequences (FSerializer &arc); protected: DSeqNode (); diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 2149f7814d..866af965a1 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -50,8 +50,9 @@ #include "timidity/timidity.h" #include "g_level.h" #include "po_man.h" -#include "farchive.h" +#include "serializer.h" #include "d_player.h" +#include "r_state.h" // MACROS ------------------------------------------------------------------ @@ -445,7 +446,7 @@ void S_Start () // Don't start the music if loading a savegame, because the music is stored there. // Don't start the music if revisiting a level in a hub for the same reason. - if (!savegamerestore && (level.info == NULL || level.info->snapshot == NULL || !level.info->isValid())) + if (!savegamerestore && (level.info == nullptr || level.info->Snapshot.mBuffer == nullptr || !level.info->isValid())) { if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid)) S_ChangeMusic (level.Music, level.musicorder); @@ -2033,8 +2034,8 @@ static void S_SetListener(SoundListener &listener, AActor *listenactor) listener.velocity.Zero(); listener.position = listenactor->SoundPos(); listener.underwater = listenactor->waterlevel == 3; - assert(zones != NULL); - listener.Environment = zones[listenactor->Sector->ZoneNumber].Environment; + assert(Zones.Size() > listenactor->Sector->ZoneNumber); + listener.Environment = Zones[listenactor->Sector->ZoneNumber].Environment; listener.valid = true; } else @@ -2208,56 +2209,41 @@ void S_StopChannel(FSoundChan *chan) //========================================================================== // -// (FArchive &) << (FSoundID &) +// // //========================================================================== -FArchive &operator<<(FArchive &arc, FSoundID &sid) +static FSerializer &Serialize(FSerializer &arc, const char *key, FSoundChan &chan, FSoundChan *def) { - if (arc.IsStoring()) + if (arc.BeginObject(key)) { - arc.WriteName((const char *)sid); - } - else - { - sid = arc.ReadName(); - } - return arc; -} + arc("sourcetype", chan.SourceType) + ("soundid", chan.SoundID) + ("orgid", chan.OrgID) + ("volume", chan.Volume) + ("distancescale", chan.DistanceScale) + ("pitch", chan.Pitch) + ("chanflags", chan.ChanFlags) + ("entchannel", chan.EntChannel) + ("priority", chan.Priority) + ("nearlimit", chan.NearLimit) + ("starttime", chan.StartTime.AsOne) + ("rolloftype", chan.Rolloff.RolloffType) + ("rolloffmin", chan.Rolloff.MinDistance) + ("rolloffmax", chan.Rolloff.MaxDistance) + ("limitrange", chan.LimitRange); -//========================================================================== -// -// (FArchive &) << (FSoundChan &) -// -//========================================================================== - -static FArchive &operator<<(FArchive &arc, FSoundChan &chan) -{ - arc << chan.SourceType; - switch (chan.SourceType) - { - case SOURCE_None: break; - case SOURCE_Actor: arc << chan.Actor; break; - case SOURCE_Sector: arc << chan.Sector; break; - case SOURCE_Polyobj: arc << chan.Poly; break; - case SOURCE_Unattached: arc << chan.Point[0] << chan.Point[1] << chan.Point[2]; break; - default: I_Error("Unknown sound source type %d\n", chan.SourceType); break; + switch (chan.SourceType) + { + case SOURCE_None: break; + case SOURCE_Actor: arc("actor", chan.Actor); break; + case SOURCE_Sector: arc("sector", chan.Sector); break; + case SOURCE_Polyobj: arc("poly", chan.Poly); break; + case SOURCE_Unattached: arc.Array("point", chan.Point, 3); break; + default: I_Error("Unknown sound source type %d\n", chan.SourceType); break; + } + arc.EndObject(); } - arc << chan.SoundID - << chan.OrgID - << chan.Volume - << chan.DistanceScale - << chan.Pitch - << chan.ChanFlags - << chan.EntChannel - << chan.Priority - << chan.NearLimit - << chan.StartTime - << chan.Rolloff.RolloffType - << chan.Rolloff.MinDistance - << chan.Rolloff.MaxDistance - << chan.LimitRange; - return arc; } @@ -2267,13 +2253,13 @@ static FArchive &operator<<(FArchive &arc, FSoundChan &chan) // //========================================================================== -void S_SerializeSounds(FArchive &arc) +void S_SerializeSounds(FSerializer &arc) { FSoundChan *chan; GSnd->Sync(true); - if (arc.IsStoring()) + if (arc.isWriting()) { TArray chans; @@ -2290,16 +2276,17 @@ void S_SerializeSounds(FArchive &arc) chans.Push(chan); } } - - arc.WriteCount(chans.Size()); - - for (unsigned int i = chans.Size(); i-- != 0; ) + if (chans.Size() > 0 && arc.BeginArray("sounds")) { - // Replace start time with sample position. - QWORD start = chans[i]->StartTime.AsOne; - chans[i]->StartTime.AsOne = GSnd ? GSnd->GetPosition(chans[i]) : 0; - arc << *chans[i]; - chans[i]->StartTime.AsOne = start; + for (unsigned int i = chans.Size(); i-- != 0; ) + { + // Replace start time with sample position. + QWORD start = chans[i]->StartTime.AsOne; + chans[i]->StartTime.AsOne = GSnd ? GSnd->GetPosition(chans[i]) : 0; + arc(nullptr, *chans[i]); + chans[i]->StartTime.AsOne = start; + } + arc.EndArray(); } } else @@ -2307,13 +2294,17 @@ void S_SerializeSounds(FArchive &arc) unsigned int count; S_StopAllChannels(); - count = arc.ReadCount(); - for (unsigned int i = 0; i < count; ++i) + if (arc.BeginArray("sounds")) { - chan = (FSoundChan*)S_GetChannel(NULL); - arc << *chan; - // Sounds always start out evicted when restored from a save. - chan->ChanFlags |= CHAN_EVICTED | CHAN_ABSTIME; + count = arc.ArraySize(); + for (unsigned int i = 0; i < count; ++i) + { + chan = (FSoundChan*)S_GetChannel(NULL); + arc(nullptr, *chan); + // Sounds always start out evicted when restored from a save. + chan->ChanFlags |= CHAN_EVICTED | CHAN_ABSTIME; + } + arc.EndArray(); } // The two tic delay is to make sure any screenwipes have finished. // This needs to be two because the game is run for one tic before @@ -2324,7 +2315,6 @@ void S_SerializeSounds(FArchive &arc) // sounds might be heard briefly before pausing for the wipe. RestartEvictionsAt = level.time + 2; } - DSeqNode::SerializeSequences(arc); GSnd->Sync(false); GSnd->UpdateSounds(); } @@ -2618,13 +2608,13 @@ void S_MIDIDeviceChanged() // //========================================================================== -int S_GetMusic (char **name) +int S_GetMusic (const char **name) { int order; if (mus_playing.name.IsNotEmpty()) { - *name = copystring (mus_playing.name); + *name = mus_playing.name; order = mus_playing.baseorder; } else @@ -3021,7 +3011,7 @@ CCMD (cachesound) for (int i = 1; i < argv.argc(); ++i) { FSoundID sfxnum = argv[i]; - if (sfxnum != 0) + if (sfxnum != FSoundID(0)) { S_CacheSound (&S_sfx[sfxnum]); } diff --git a/src/s_sound.h b/src/s_sound.h index e157bbaa1b..9b917e25c8 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -126,6 +126,14 @@ public: ID = S_FindSound(name.GetChars()); return *this; } + bool operator !=(FSoundID other) const + { + return ID != other.ID; + } + bool operator !=(int other) const + { + return ID != other; + } operator int() const { return ID; @@ -168,8 +176,6 @@ public: } }; -FArchive &operator<<(FArchive &arc, FSoundID &sid); - extern FRolloffInfo S_Rolloff; extern BYTE *S_SoundCurve; extern int S_SoundCurveSize; @@ -313,7 +319,7 @@ bool S_ChangeSoundVolume(AActor *actor, int channel, float volume); void S_RelinkSound (AActor *from, AActor *to); // Stores/retrieves playing channel information in an archive. -void S_SerializeSounds(FArchive &arc); +void S_SerializeSounds(FSerializer &arc); // Start music using bool S_StartMusic (const char *music_name); @@ -328,7 +334,7 @@ void S_RestartMusic (); void S_MIDIDeviceChanged(); -int S_GetMusic (char **name); +int S_GetMusic (const char **name); // Stops the music for sure. void S_StopMusic (bool force); @@ -379,9 +385,6 @@ void S_NoiseDebug (); extern ReverbContainer *Environments; extern ReverbContainer *DefaultEnvironments[26]; -class FArchive; -FArchive &operator<< (FArchive &arc, ReverbContainer *&env); - void S_SetEnvironment (const ReverbContainer *settings); ReverbContainer *S_FindEnvironment (const char *name); ReverbContainer *S_FindEnvironment (int id); diff --git a/src/serializer.cpp b/src/serializer.cpp new file mode 100644 index 0000000000..ae1c55a7a7 --- /dev/null +++ b/src/serializer.cpp @@ -0,0 +1,2117 @@ +/* +** serializer.cpp +** Savegame wrapper around RapidJSON +** +**--------------------------------------------------------------------------- +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 // disable this insanity which is bound to make the code break over time. +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseFullPrecisionFlag + +#include "rapidjson/rapidjson.h" +#include "rapidjson/writer.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/document.h" +#include "serializer.h" +#include "r_data/colormaps.h" +#include "r_data/r_interpolate.h" +#include "r_defs.h" +#include "r_state.h" +#include "p_lnspec.h" +#include "i_system.h" +#include "w_wad.h" +#include "p_terrain.h" +#include "c_dispatch.h" +#include "p_setup.h" +#include "p_conversation.h" +#include "dsectoreffect.h" +#include "d_player.h" +#include "r_data/r_interpolate.h" +#include "g_shared/a_sharedglobal.h" +#include "po_man.h" +#include "v_font.h" +#include "w_zip.h" +#include "doomerrors.h" +#include "v_text.h" + +char nulspace[1024 * 1024 * 4]; +bool save_full = false; // for testing. Should be removed afterward. + +//========================================================================== +// +// +// +//========================================================================== + +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 String(const char *k) + { + if (mWriter1) mWriter1->String(k); + else if (mWriter2) mWriter2->String(k); + } + + void String(const char *k, int size) + { + if (mWriter1) mWriter1->String(k, size); + else if (mWriter2) mWriter2->String(k, size); + } + + 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; + int mPlayers[MAXPLAYERS]; + bool mObjectsRead = false; + + FReader(const char *buffer, size_t length) + { + rapidjson::Document doc; + mDoc.Parse(buffer, length); + mObjects.Push(FJSONObject(&mDoc)); + memset(mPlayers, -1, sizeof(mPlayers)); + } + + 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; + } +}; + + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::OpenWriter(bool pretty) +{ + if (w != nullptr || r != nullptr) return false; + + mErrors = 0; + w = new FWriter(pretty); + BeginObject(nullptr); + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::OpenReader(const char *buffer, size_t length) +{ + if (w != nullptr || r != nullptr) return false; + + mErrors = 0; + r = new FReader(buffer, length); + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::OpenReader(FCompressedBuffer *input) +{ + if (input->mSize <= 0 || input->mBuffer == nullptr) return false; + if (w != nullptr || r != nullptr) return false; + + mErrors = 0; + if (input->mMethod == METHOD_STORED) + { + r = new FReader((char*)input->mBuffer, input->mSize); + } + else + { + char *unpacked = new char[input->mSize]; + input->Decompress(unpacked); + r = new FReader(unpacked, input->mSize); + delete[] unpacked; + } + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSerializer::Close() +{ + if (w != nullptr) + { + delete w; + w = nullptr; + } + if (r != nullptr) + { + // we must explicitly delete all thinkers in the array which did not get linked into the thinker lists. + // Otherwise these objects may survive a level deletion and point to incorrect data. + for (auto &obj : r->mDObjects) + { + auto think = dyn_cast(obj); + if (think != nullptr) + { + if (think->NextThinker == nullptr || think->PrevThinker == nullptr) + { + think->Destroy(); + } + } + } + + delete r; + r = nullptr; + } + if (mErrors > 0) + { + I_Error("%d errors parsing JSON", mErrors); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +int FSerializer::ArraySize() +{ + if (r != nullptr && r->mObjects.Last().mObject->IsArray()) + { + return r->mObjects.Last().mObject->Size(); + } + else + { + return 0; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::canSkip() const +{ + return isWriting() && w->inObject(); +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSerializer::WriteKey(const char *key) +{ + if (isWriting() && w->inObject()) + { + assert(key != nullptr); + if (key == nullptr) + { + I_Error("missing element name"); + } + w->Key(key); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::BeginObject(const char *name) +{ + if (isWriting()) + { + WriteKey(name); + w->StartObject(); + w->mInObject.Push(true); + } + else + { + auto val = r->FindKey(name); + if (val != nullptr) + { + assert(val->IsObject()); + if (val->IsObject()) + { + r->mObjects.Push(FJSONObject(val)); + } + else + { + Printf(TEXTCOLOR_RED "Object expected for '%s'", name); + mErrors++; + return false; + } + } + else + { + return false; + } + } + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSerializer::EndObject() +{ + if (isWriting()) + { + if (w->inObject()) + { + w->EndObject(); + w->mInObject.Pop(); + } + else + { + assert(false && "EndObject call not inside an object"); + I_Error("EndObject call not inside an object"); + } + } + else + { + r->mObjects.Pop(); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FSerializer::BeginArray(const char *name) +{ + if (isWriting()) + { + WriteKey(name); + w->StartArray(); + w->mInObject.Push(false); + } + else + { + auto val = r->FindKey(name); + if (val != nullptr) + { + assert(val->IsArray()); + if (val->IsArray()) + { + r->mObjects.Push(FJSONObject(val)); + } + else + { + Printf(TEXTCOLOR_RED "Array expected for '%s'", name); + mErrors++; + return false; + } + } + else + { + return false; + } + } + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FSerializer::EndArray() +{ + if (isWriting()) + { + if (!w->inObject()) + { + w->EndArray(); + w->mInObject.Pop(); + } + else + { + assert(false && "EndArray call not inside an array"); + I_Error("EndArray call not inside an array"); + } + } + else + { + r->mObjects.Pop(); + } +} + +//========================================================================== +// +// Special handler for args (because ACS specials' arg0 needs special treatment.) +// +//========================================================================== + +FSerializer &FSerializer::Args(const char *key, int *args, int *defargs, int special) +{ + if (isWriting()) + { + if (w->inObject() && defargs != nullptr && !memcmp(args, defargs, 5 * sizeof(int))) + { + return *this; + } + + WriteKey(key); + w->StartArray(); + for (int i = 0; i < 5; i++) + { + if (i == 0 && args[i] < 0 && P_IsACSSpecial(special)) + { + w->String(FName(ENamedName(-args[i])).GetChars()); + } + else + { + w->Int(args[i]); + } + } + w->EndArray(); + } + else + { + auto val = r->FindKey(key); + if (val != nullptr) + { + if (val->IsArray()) + { + unsigned int cnt = MIN(val->Size(), 5); + for (unsigned int i = 0; i < cnt; i++) + { + const rapidjson::Value &aval = (*val)[i]; + if (aval.IsInt()) + { + args[i] = aval.GetInt(); + } + else if (i == 0 && aval.IsString()) + { + args[i] = -FName(aval.GetString()); + } + else + { + assert(false && "Integer expected"); + Printf(TEXTCOLOR_RED "Integer expected for '%s[%d]'", key, i); + mErrors++; + } + } + } + else + { + assert(false && "array expected"); + Printf(TEXTCOLOR_RED "array expected for '%s'", key); + mErrors++; + } + } + } + return *this; +} + +//========================================================================== +// +// Special handler for script numbers +// +//========================================================================== + +FSerializer &FSerializer::ScriptNum(const char *key, int &num) +{ + if (isWriting()) + { + WriteKey(key); + if (num < 0) + { + w->String(FName(ENamedName(-num)).GetChars()); + } + else + { + w->Int(num); + } + } + else + { + auto val = r->FindKey(key); + if (val != nullptr) + { + if (val->IsInt()) + { + num = val->GetInt(); + } + else if (val->IsString()) + { + num = -FName(val->GetString()); + } + else + { + assert(false && "Integer expected"); + Printf(TEXTCOLOR_RED "Integer expected for '%s'", key); + mErrors++; + } + } + } + return *this; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &FSerializer::Terrain(const char *key, int &terrain, int *def) +{ + if (isWriting() && def != nullptr && terrain == *def) + { + return *this; + } + FName terr = P_GetTerrainName(terrain); + Serialize(*this, key, terr, nullptr); + if (isReading()) + { + terrain = P_FindTerrain(terr); + } + return *this; +} + +//========================================================================== +// +// +// +//========================================================================== + +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->String(sprites[spritenum].name, 4); + } + else + { + auto val = r->FindKey(key); + if (val != nullptr) + { + if (val->IsString()) + { + int name = *reinterpret_cast(val->GetString()); + for (auto hint = NumStdSprites; hint-- != 0; ) + { + if (sprites[hint].dwName == name) + { + spritenum = hint; + break; + } + } + } + } + } + return *this; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &FSerializer::StringPtr(const char *key, const char *&charptr) +{ + if (isWriting()) + { + WriteKey(key); + if (charptr != nullptr) + w->String(charptr); + else + w->Null(); + } + else + { + auto val = r->FindKey(key); + if (val != nullptr) + { + if (val->IsString()) + { + charptr = val->GetString(); + } + else + { + charptr = nullptr; + } + } + } + return *this; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &FSerializer::AddString(const char *key, const char *charptr) +{ + if (isWriting()) + { + WriteKey(key); + w->String(charptr); + } + return *this; +} + +//========================================================================== +// +// +// +//========================================================================== + +unsigned FSerializer::GetSize(const char *group) +{ + if (isWriting()) return -1; // we do not know this when writing. + + const rapidjson::Value &val = r->mDoc[group]; + if (!val.IsArray()) return -1; + return val.Size(); +} + +//========================================================================== +// +// gets the key pointed to by the iterator, caches its value +// and returns the key string. +// +//========================================================================== + +const char *FSerializer::GetKey() +{ + if (isWriting()) return nullptr; // we do not know this when writing. + if (!r->mObjects.Last().mObject->IsObject()) return nullptr; // non-objects do not have keys. + auto &it = r->mObjects.Last().mIterator; + if (it == r->mObjects.Last().mObject->MemberEnd()) return nullptr; + r->mKeyValue = &it->value; + return (it++)->name.GetString(); +} + +//========================================================================== +// +// Writes out all collected objects +// +//========================================================================== + +void FSerializer::WriteObjects() +{ + if (isWriting() && w->mDObjects.Size()) + { + BeginArray("objects"); + // we cannot use the C++11 shorthand syntax here because the array may grow while being processed. + for (unsigned i = 0; i < w->mDObjects.Size(); i++) + { + auto obj = w->mDObjects[i]; + player_t *player; + + BeginObject(nullptr); + w->Key("classtype"); + w->String(obj->GetClass()->TypeName.GetChars()); + + obj->SerializeUserVars(*this); + obj->Serialize(*this); + obj->CheckIfSerialized(); + EndObject(); + } + EndArray(); + } +} + +//========================================================================== +// +// Writes out all collected objects +// +//========================================================================== + +void FSerializer::ReadObjects(bool hubtravel) +{ + 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 iteration: create all the objects but do nothing with them yet. + 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. + int pindex = -1; + + Serialize(*this, "classtype", clsname, nullptr); + PClass *cls = PClass::FindClass(clsname); + if (cls == nullptr) + { + Printf("Unknown object class '%d' in savegame", clsname.GetChars()); + founderrors = true; + r->mDObjects[i] = RUNTIME_CLASS(AActor)->CreateNew(); // make sure we got at least a valid pointer for the duration of the loading process. + r->mDObjects[i]->Destroy(); // but we do not want to keep this around, so destroy it right away. + } + else + { + r->mDObjects[i] = cls->CreateNew(); + } + EndObject(); + } + } + // Now that everything has been created and we can retrieve the pointers we can deserialize it. + r->mObjectsRead = true; + + if (!founderrors) + { + // Reset to start; + r->mObjects.Last().mIndex = 0; + + for (unsigned i = 0; i < r->mDObjects.Size(); i++) + { + auto obj = r->mDObjects[i]; + if (BeginObject(nullptr)) + { + if (obj != nullptr) + { + int pindex = -1; + try + { + obj->SerializeUserVars(*this); + obj->Serialize(*this); + } + 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", err.GetMessage(), obj ? obj->GetClass()->TypeName.GetChars() : "invalid object"); + mErrors++; + } + } + EndObject(); + } + } + } + EndArray(); + + DThinker::bSerialOverride = false; + assert(!founderrors); + if (founderrors) + { + Printf(TEXTCOLOR_RED "Failed to restore all objects in savegame"); + mErrors++; + } + } + catch(...) + { + // nuke all objects we created here. + for (auto obj : r->mDObjects) + { + obj->Destroy(); + } + r->mDObjects.Clear(); + + // make sure this flag gets unset, even if something in here throws an error. + DThinker::bSerialOverride = false; + throw; + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +const char *FSerializer::GetOutput(unsigned *len) +{ + if (isReading()) return nullptr; + WriteObjects(); + EndObject(); + if (len != nullptr) + { + *len = (unsigned)w->mOutString.GetSize(); + } + return w->mOutString.GetString(); +} + +//========================================================================== +// +// +// +//========================================================================== + +FCompressedBuffer FSerializer::GetCompressedOutput() +{ + if (isReading()) return{ 0,0,0,0,0,nullptr }; + FCompressedBuffer buff; + WriteObjects(); + EndObject(); + buff.mSize = (unsigned)w->mOutString.GetSize(); + buff.mZipFlags = 0; + buff.mCRC32 = crc32(0, (const Bytef*)w->mOutString.GetString(), buff.mSize); + + uint8_t *compressbuf = new uint8_t[buff.mSize+1]; + + z_stream stream; + int err; + + stream.next_in = (Bytef *)w->mOutString.GetString(); + stream.avail_in = buff.mSize; + stream.next_out = (Bytef*)compressbuf; + stream.avail_out = buff.mSize; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + // create output in zip-compatible form as required by FCompressedBuffer + err = deflateInit2(&stream, 8, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY); + if (err != Z_OK) + { + goto error; + } + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) + { + deflateEnd(&stream); + goto error; + } + buff.mCompressedSize = stream.total_out; + + err = deflateEnd(&stream); + if (err == Z_OK) + { + buff.mBuffer = new char[buff.mCompressedSize]; + buff.mMethod = METHOD_DEFLATE; + memcpy(buff.mBuffer, compressbuf, buff.mCompressedSize); + delete[] compressbuf; + return buff; + } + +error: + memcpy(compressbuf, w->mOutString.GetString(), buff.mSize + 1); + buff.mCompressedSize = buff.mSize; + buff.mMethod = METHOD_STORED; + return buff; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Bool(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsBool()); + if (val->IsBool()) + { + value = val->GetBool(); + } + else + { + Printf(TEXTCOLOR_RED "boolean type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, int64_t &value, int64_t *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Int64(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsInt64()); + if (val->IsInt64()) + { + value = val->GetInt64(); + } + else + { + Printf(TEXTCOLOR_RED "integer type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, uint64_t &value, uint64_t *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Uint64(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsUint64()); + if (val->IsUint64()) + { + value = val->GetUint64(); + } + else + { + Printf(TEXTCOLOR_RED "integer type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, int32_t &value, int32_t *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Int(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsInt()); + if (val->IsInt()) + { + value = val->GetInt(); + } + else + { + Printf(TEXTCOLOR_RED "integer type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, uint32_t &value, uint32_t *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Uint(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsUint()); + if (val->IsUint()) + { + value = val->GetUint(); + } + else + { + Printf(TEXTCOLOR_RED "integer type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, int8_t &value, int8_t *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, uint8_t &value, uint8_t *defval) +{ + uint32_t vv = value; + uint32_t vvd = defval ? *defval : value - 1; + Serialize(arc, key, vv, &vvd); + value = (uint8_t)vv; + return arc; +} + +FSerializer &Serialize(FSerializer &arc, const char *key, int16_t &value, int16_t *defval) +{ + int32_t vv = value; + int32_t vvd = defval ? *defval : value - 1; + Serialize(arc, key, vv, &vvd); + value = (int16_t)vv; + return arc; +} + +FSerializer &Serialize(FSerializer &arc, const char *key, uint16_t &value, uint16_t *defval) +{ + uint32_t vv = value; + uint32_t vvd = defval ? *defval : value - 1; + Serialize(arc, key, vv, &vvd); + value = (uint16_t)vv; + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->Double(value); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsDouble()); + if (val->IsDouble()) + { + value = val->GetDouble(); + } + else + { + Printf(TEXTCOLOR_RED "float type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *defval) +{ + double vv = value; + double vvd = defval ? *defval : value - 1; + Serialize(arc, key, vv, &vvd); + value = (float)vv; + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +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) + { + ptrdiff_t vv = value == nullptr ? -1 : value - base; + Serialize(arc, key, vv, nullptr); + value = vv < 0 ? nullptr : base + vv; + } + return arc; +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval) +{ + return SerializePointer(arc, key, value, defval, polyobjs); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval) +{ + return SerializePointer(arc, key, value, defval, polyobjs); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval) +{ + return SerializePointer(arc, key, value, defval, sides); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval) +{ + return SerializePointer(arc, key, value, defval, sectors); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval) +{ + return SerializePointer(arc, key, value, defval, sectors); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval) +{ + return SerializePointer(arc, key, value, defval, players); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval) +{ + return SerializePointer(arc, key, value, defval, lines); +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, vertex_t *&value, vertex_t **defval) +{ + return SerializePointer(arc, key, value, defval, vertexes); +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + if (!value.Exists()) + { + arc.WriteKey(key); + arc.w->Null(); + return arc; + } + if (value.isNull()) + { + // save 'no texture' in a more space saving way + arc.WriteKey(key); + arc.w->Int(0); + return arc; + } + FTextureID chk = value; + if (chk.GetIndex() >= TexMan.NumTextures()) chk.SetNull(); + FTexture *pic = TexMan[chk]; + const char *name; + + if (Wads.GetLinkedTexture(pic->SourceLump) == pic) + { + name = Wads.GetLumpFullName(pic->SourceLump); + } + else + { + name = pic->Name; + } + arc.WriteKey(key); + arc.w->StartArray(); + arc.w->String(name); + arc.w->Int(pic->UseType); + arc.w->EndArray(); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsArray()) + { + const rapidjson::Value &nameval = (*val)[0]; + const rapidjson::Value &typeval = (*val)[1]; + assert(nameval.IsString() && typeval.IsInt()); + if (nameval.IsString() && typeval.IsInt()) + { + value = TexMan.GetTexture(nameval.GetString(), typeval.GetInt()); + } + else + { + Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key); + value.SetNull(); + arc.mErrors++; + } + } + else if (val->IsNull()) + { + value.SetInvalid(); + } + else if (val->IsInt() && val->GetInt() == 0) + { + value.SetNull(); + } + else + { + assert(false && "not a texture"); + Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key); + value.SetNull(); + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// This never uses defval and instead uses 'null' as default +// because object pointers cannot be safely defaulted to anything else. +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode) +{ + if (retcode) *retcode = true; + if (arc.isWriting()) + { + if (value != nullptr) + { + int ndx; + if (value == WP_NOCHANGE) + { + ndx = -1; + } + else if (value->ObjectFlags & OF_EuthanizeMe) + { + return arc; + } + else + { + int *pndx = arc.w->mObjectMap.CheckKey(value); + if (pndx != nullptr) + { + ndx = *pndx; + } + else + { + ndx = arc.w->mDObjects.Push(value); + arc.w->mObjectMap[value] = ndx; + } + } + Serialize(arc, key, ndx, nullptr); + } + else if (!arc.w->inObject()) + { + arc.w->Null(); + } + } + else + { + if (!arc.r->mObjectsRead) + { + // If you want to read objects, you MUST call ReadObjects first, even if there's only nullptr's. + assert(false && "Attempt to read object reference without calling ReadObjects first"); + I_Error("Attempt to read object reference without calling ReadObjects first"); + } + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsNull()) + { + value = nullptr; + return arc; + } + else if (val->IsInt()) + { + int index = val->GetInt(); + if (index == -1) + { + value = WP_NOCHANGE; + } + else + { + assert(index >= 0 && index < (int)arc.r->mDObjects.Size()); + if (index >= 0 && index < (int)arc.r->mDObjects.Size()) + { + value = arc.r->mDObjects[index]; + } + else + { + assert(false && "invalid object reference"); + Printf(TEXTCOLOR_RED "Invalid object reference for '%s'", key); + value = nullptr; + arc.mErrors++; + if (retcode) *retcode = false; + } + } + return arc; + } + } + if (!retcode) + { + value = nullptr; + } + else + { + *retcode = false; + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + arc.w->String(value.GetChars()); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsString()); + if (val->IsString()) + { + value = val->GetString(); + } + else + { + Printf(TEXTCOLOR_RED "String expected for '%s'", key); + arc.mErrors++; + value = NAME_None; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicColormap *&cm, FDynamicColormap **def) +{ + if (arc.isWriting()) + { + if (arc.w->inObject() && def != nullptr && cm->Color == (*def)->Color && cm->Fade == (*def)->Fade && cm->Desaturate == (*def)->Desaturate) + { + return arc; + } + + arc.WriteKey(key); + arc.w->StartArray(); + arc.w->Uint(cm->Color); + arc.w->Uint(cm->Fade); + arc.w->Uint(cm->Desaturate); + arc.w->EndArray(); + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsArray()) + { + const rapidjson::Value &colorval = (*val)[0]; + const rapidjson::Value &fadeval = (*val)[1]; + const rapidjson::Value &desatval = (*val)[2]; + if (colorval.IsUint() && fadeval.IsUint() && desatval.IsUint()) + { + cm = GetSpecialLights(colorval.GetUint(), fadeval.GetUint(), desatval.GetUint()); + return arc; + } + } + assert(false && "not a colormap"); + Printf(TEXTCOLOR_RED "object does not represent a colormap for '%s'", key); + cm = &NormalLight; + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || sid != *def) + { + arc.WriteKey(key); + const char *sn = (const char*)sid; + if (sn != nullptr) arc.w->String(sn); + else arc.w->Null(); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsString() || val->IsNull()); + if (val->IsString()) + { + sid = val->GetString(); + } + else if (val->IsNull()) + { + sid = 0; + } + else + { + Printf(TEXTCOLOR_RED "string type expected for '%s'", key); + sid = 0; + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor *&clst, PClassActor **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || clst != *def) + { + arc.WriteKey(key); + if (clst == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->String(clst->TypeName.GetChars()); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsString() || val->IsNull()); + if (val->IsString()) + { + clst = PClass::FindActor(val->GetString()); + } + else if (val->IsNull()) + { + clst = nullptr; + } + else + { + Printf(TEXTCOLOR_RED "string type expected for '%s'", key); + clst = nullptr; + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// almost, but not quite the same as the above. +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || clst != *def) + { + arc.WriteKey(key); + if (clst == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->String(clst->TypeName.GetChars()); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsString()) + { + clst = PClass::FindClass(val->GetString()); + } + else if (val->IsNull()) + { + clst = nullptr; + } + else + { + Printf(TEXTCOLOR_RED "string type expected for '%s'", key); + clst = nullptr; + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def, bool *retcode) +{ + if (retcode) *retcode = false; + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || state != *def) + { + if (retcode) *retcode = true; + arc.WriteKey(key); + if (state == nullptr) + { + arc.w->Null(); + } + else + { + PClassActor *info = FState::StaticFindStateOwner(state); + + if (info != NULL) + { + arc.w->StartArray(); + arc.w->String(info->TypeName.GetChars()); + arc.w->Uint((uint32_t)(state - info->OwnedStates)); + arc.w->EndArray(); + } + else + { + arc.w->Null(); + } + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsNull()) + { + if (retcode) *retcode = true; + state = nullptr; + } + else if (val->IsArray()) + { + if (retcode) *retcode = true; + const rapidjson::Value &cls = (*val)[0]; + const rapidjson::Value &ndx = (*val)[1]; + + state = nullptr; + assert(cls.IsString() && ndx.IsUint()); + if (cls.IsString() && ndx.IsUint()) + { + PClassActor *clas = PClass::FindActor(cls.GetString()); + if (clas && ndx.GetUint() < (unsigned)clas->NumOwnedStates) + { + state = clas->OwnedStates + ndx.GetUint(); + } + else + { + // this can actually happen by changing the DECORATE so treat it as a warning, not an error. + state = nullptr; + Printf(TEXTCOLOR_ORANGE "Invalid state '%s+%d' for '%s'", cls.GetString(), ndx.GetInt(), key); + } + } + else + { + assert(false && "not a state"); + Printf(TEXTCOLOR_RED "data does not represent a state for '%s'", key); + arc.mErrors++; + } + } + else if (!retcode) + { + assert(false && "not an array"); + Printf(TEXTCOLOR_RED "array type expected for '%s'", key); + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDialogueNode *&node, FStrifeDialogueNode **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || node != *def) + { + arc.WriteKey(key); + if (node == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->Uint(node->ThisNodeNum); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsUint() || val->IsNull()); + if (val->IsNull()) + { + node = nullptr; + } + else if (val->IsUint()) + { + if (val->GetUint() >= StrifeDialogues.Size()) + { + node = nullptr; + } + else + { + node = StrifeDialogues[val->GetUint()]; + } + } + else + { + Printf(TEXTCOLOR_RED "integer expected for '%s'", key); + arc.mErrors++; + node = nullptr; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&pstr, FString **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || pstr != *def) + { + arc.WriteKey(key); + if (pstr == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->String(pstr->GetChars()); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsNull() || val->IsString()); + if (val->IsNull()) + { + pstr = nullptr; + } + else if (val->IsString()) + { + pstr = AActor::mStringPropertyData.Alloc(val->GetString()); + } + else + { + Printf(TEXTCOLOR_RED "string expected for '%s'", key); + pstr = nullptr; + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString *def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || pstr.Compare(*def) != 0) + { + arc.WriteKey(key); + arc.w->String(pstr.GetChars()); + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsNull() || val->IsString()); + if (val->IsNull()) + { + pstr = ""; + } + else if (val->IsString()) + { + pstr = val->GetString(); + } + else + { + Printf(TEXTCOLOR_RED "string expected for '%s'", key); + pstr = ""; + arc.mErrors++; + } + } + } + return arc; + +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr, char **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || strcmp(pstr, *def)) + { + arc.WriteKey(key); + if (pstr == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->String(pstr); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + assert(val->IsNull() || val->IsString()); + if (val->IsNull()) + { + pstr = nullptr; + } + else if (val->IsString()) + { + pstr = copystring(val->GetString()); + } + else + { + Printf(TEXTCOLOR_RED "string expected for '%s'", key); + pstr = nullptr; + arc.mErrors++; + } + } + } + return arc; +} + +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def) +{ + if (arc.isWriting()) + { + const char *n = font->GetName(); + return arc.StringPtr(key, n); + } + else + { + const char *n; + arc.StringPtr(key, n); + font = V_GetFont(n); + if (font == nullptr) + { + Printf(TEXTCOLOR_ORANGE "Could not load font %s\n", n); + font = SmallFont; + } + return arc; + } + +} + +//========================================================================== +// +// Handler to retrieve a numeric value of any kind. +// +//========================================================================== + +FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &value, NumericValue *defval) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || defval == nullptr || value != *defval) + { + arc.WriteKey(key); + switch (value.type) + { + case NumericValue::NM_signed: + arc.w->Int64(value.signedval); + break; + case NumericValue::NM_unsigned: + arc.w->Uint64(value.unsignedval); + break; + case NumericValue::NM_float: + arc.w->Double(value.floatval); + break; + default: + arc.w->Null(); + break; + } + } + } + else + { + auto val = arc.r->FindKey(key); + value.signedval = 0; + value.type = NumericValue::NM_invalid; + if (val != nullptr) + { + if (val->IsUint64()) + { + value.unsignedval = val->GetUint64(); + value.type = NumericValue::NM_unsigned; + } + else if (val->IsInt64()) + { + value.signedval = val->GetInt64(); + value.type = NumericValue::NM_signed; + } + else if (val->IsDouble()) + { + value.floatval = val->GetDouble(); + value.type = NumericValue::NM_float; + } + } + } + return arc; +} diff --git a/src/serializer.h b/src/serializer.h new file mode 100644 index 0000000000..f1d35e1f88 --- /dev/null +++ b/src/serializer.h @@ -0,0 +1,302 @@ +#ifndef __SERIALIZER_H +#define __SERIALIZER_H + +#include +#include +#include "tarray.h" +#include "r_defs.h" +#include "resourcefiles/file_zip.h" + +extern bool save_full; + +struct ticcmd_t; +struct usercmd_t; + +struct FWriter; +struct FReader; + +extern TArray loadsectors; +extern TArray loadlines; +extern TArray loadsides; + +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 +{ + enum EType + { + NM_invalid, + NM_signed, + NM_unsigned, + NM_float + } type; + + union + { + int64_t signedval; + uint64_t unsignedval; + double floatval; + }; + + bool operator !=(const NumericValue &other) + { + return type != other.type || signedval != other.signedval; + } +}; + + +class FSerializer +{ + +public: + FWriter *w = nullptr; + FReader *r = nullptr; + + int ArraySize(); + void WriteKey(const char *key); + void WriteObjects(); + +public: + + ~FSerializer() + { + Close(); + } + bool OpenWriter(bool pretty = true); + bool OpenReader(const char *buffer, size_t length); + bool OpenReader(FCompressedBuffer *input); + void Close(); + void ReadObjects(bool hubtravel); + bool BeginObject(const char *name); + void EndObject(); + bool BeginArray(const char *name); + void EndArray(); + unsigned GetSize(const char *group); + const char *GetKey(); + const char *GetOutput(unsigned *len = nullptr); + FCompressedBuffer GetCompressedOutput(); + 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); + 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); + FSerializer &ScriptNum(const char *key, int &num); + bool isReading() const + { + return r != nullptr; + } + + bool isWriting() const + { + return w != nullptr; + } + + bool canSkip() const; + + template + FSerializer &operator()(const char *key, T &obj) + { + return Serialize(*this, key, obj, (T*)nullptr); + } + + template + FSerializer &operator()(const char *key, T &obj, T &def) + { + return Serialize(*this, key, obj, save_full? nullptr : &def); + } + + template + FSerializer &Array(const char *key, T *obj, int count, bool fullcompare = false) + { + if (!save_full && fullcompare && isWriting() && nullcmp(obj, count * sizeof(T))) + { + return *this; + } + + if (BeginArray(key)) + { + if (isReading()) + { + int max = ArraySize(); + if (max < count) count = max; + } + for (int i = 0; i < count; i++) + { + Serialize(*this, nullptr, obj[i], (T*)nullptr); + } + EndArray(); + } + return *this; + } + + template + FSerializer &Array(const char *key, T *obj, T *def, int count, bool fullcompare = false) + { + if (!save_full && fullcompare && isWriting() && def != nullptr && !memcmp(obj, def, count * sizeof(T))) + { + return *this; + } + if (BeginArray(key)) + { + if (isReading()) + { + int max = ArraySize(); + if (max < count) count = max; + } + for (int i = 0; i < count; i++) + { + Serialize(*this, nullptr, obj[i], def ? &def[i] : nullptr); + } + EndArray(); + } + return *this; + } + + template + FSerializer &Enum(const char *key, T &obj) + { + auto val = (std::underlying_type::type)obj; + Serialize(*this, key, val, nullptr); + obj = (T)val; + return *this; + } + + int mErrors = 0; +}; + +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); +FSerializer &Serialize(FSerializer &arc, const char *key, int32_t &value, int32_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, uint32_t &value, uint32_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, int8_t &value, int8_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, uint8_t &value, uint8_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, int16_t &value, int16_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, uint16_t &value, uint16_t *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode = nullptr); +FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def); +FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def); +FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); +FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &sid, ticcmd_t *def); +FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercmd_t *def); + +template +FSerializer &Serialize(FSerializer &arc, const char *key, T *&value, T **) +{ + DObject *v = static_cast(value); + Serialize(arc, key, v, nullptr); + value = static_cast(v); + return arc; +} + +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, TArray &value, TArray *) +{ + if (arc.isWriting()) + { + if (value.Size() == 0) return arc; // do not save empty arrays + } + bool res = arc.BeginArray(key); + if (arc.isReading()) + { + if (!res) + { + value.Clear(); + return arc; + } + value.Resize(arc.ArraySize()); + } + for (unsigned i = 0; i < value.Size(); i++) + { + Serialize(arc, nullptr, value[i], (T*)nullptr); + } + arc.EndArray(); + return arc; +} + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, vertex_t *&value, vertex_t **defval); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicColormap *&cm, FDynamicColormap **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor *&clst, PClassActor **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDialogueNode *&node, FStrifeDialogueNode **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&pstr, FString **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDoorAnimation *&pstr, FDoorAnimation **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr, char **def); +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def); + +template<> inline FSerializer &Serialize(FSerializer &arc, const char *key, PClassPlayerPawn *&clst, PClassPlayerPawn **def) +{ + return Serialize(arc, key, (PClassActor *&)clst, (PClassActor **)def); +} + +FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def, bool *retcode); +template<> inline FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def) +{ + return Serialize(arc, key, state, def, nullptr); +} + + +inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector3 &p, DVector3 *def) +{ + return arc.Array(key, &p[0], def? &(*def)[0] : nullptr, 3, true); +} + +inline FSerializer &Serialize(FSerializer &arc, const char *key, DRotator &p, DRotator *def) +{ + return arc.Array(key, &p[0], def? &(*def)[0] : nullptr, 3, true); +} + +inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector2 &p, DVector2 *def) +{ + return arc.Array(key, &p[0], def? &(*def)[0] : nullptr, 2, true); +} + +inline FSerializer &Serialize(FSerializer &arc, const char *key, DAngle &p, DAngle *def) +{ + return Serialize(arc, key, p.Degrees, def? &def->Degrees : nullptr); +} + +inline FSerializer &Serialize(FSerializer &arc, const char *key, PalEntry &pe, PalEntry *def) +{ + return Serialize(arc, key, pe.d, def? &def->d : nullptr); +} + +inline FSerializer &Serialize(FSerializer &arc, const char *key, FRenderStyle &style, FRenderStyle *def) +{ + return arc.Array(key, &style.BlendOp, def ? &def->BlendOp : nullptr, 4); +} + +template +FSerializer &Serialize(FSerializer &arc, const char *key, TFlags &flags, TFlags *def) +{ + return Serialize(arc, key, flags.Value, def? &def->Value : nullptr); +} + + +#endif \ No newline at end of file diff --git a/src/statistics.cpp b/src/statistics.cpp index 5d14bad0a0..92a459e150 100644 --- a/src/statistics.cpp +++ b/src/statistics.cpp @@ -70,7 +70,7 @@ #include "r_sky.h" #include "p_lnspec.h" #include "m_crc32.h" -#include "farchive.h" +#include "serializer.h" CVAR(Int, savestatistics, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(String, statfile, "zdoomstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -87,7 +87,7 @@ struct OneLevel int totalkills, killcount; int totalsecrets, secretcount; int leveltime; - char levelname[9]; + FString Levelname; }; // Current game's statistics @@ -408,13 +408,12 @@ static void StoreLevelStats() { for(i=0;imEpisodeMap; - arc << startlevel; - } - for(int j = 0; j < i; j++) - { - OneLevel &l = LevelData[j]; - - arc << l.totalkills - << l.killcount - << l.totalsecrets - << l.secretcount - << l.leveltime; - - if (arc.IsStoring()) arc.WriteName(l.levelname); - else strcpy(l.levelname, arc.ReadName()); + else + { + if (StartEpisode != NULL) startlevel = StartEpisode->mEpisodeMap; + arc("startlevel", startlevel); + } + arc("levels", LevelData); + arc.EndObject(); } } -#define STAT_ID MAKE_ID('s','T','a','t') - -void STAT_Write(FILE *file) -{ - FPNGChunkArchive arc (file, STAT_ID); - SerializeStatistics(arc); -} - -void STAT_Read(PNGHandle *png) -{ - DWORD chunkLen = (DWORD)M_FindPNGChunk (png, STAT_ID); - if (chunkLen != 0) - { - FPNGChunkArchive arc (png->File->GetFile(), STAT_ID, chunkLen); - SerializeStatistics(arc); - } -} //========================================================================== // @@ -588,7 +575,7 @@ FString GetStatString() { OneLevel *l = &LevelData[i]; compose.AppendFormat("Level %s - Kills: %d/%d - Secrets: %d/%d - Time: %d:%02d\n", - l->levelname, l->killcount, l->totalkills, l->secretcount, l->totalsecrets, + l->Levelname.GetChars(), l->killcount, l->totalkills, l->secretcount, l->totalsecrets, l->leveltime/(60*TICRATE), (l->leveltime/TICRATE)%60); } return compose; diff --git a/src/tarray.h b/src/tarray.h index 4d16ddfe2c..6e37ea0398 100644 --- a/src/tarray.h +++ b/src/tarray.h @@ -49,9 +49,6 @@ #include "m_alloc.h" -class FArchive; - - template class TIterator { public: @@ -83,8 +80,6 @@ struct FArray template class TArray { - template friend FArchive &operator<< (FArchive &arc, TArray &self); - public: typedef TIterator iterator; diff --git a/src/textures/anim_switches.cpp b/src/textures/anim_switches.cpp index 2cde1d0c8f..14fb4db84c 100644 --- a/src/textures/anim_switches.cpp +++ b/src/textures/anim_switches.cpp @@ -43,7 +43,6 @@ #include "cmdlib.h" #include "sc_man.h" #include "gi.h" -#include "farchive.h" static int SortSwitchDefs (const void *a, const void *b) @@ -398,24 +397,3 @@ FSwitchDef *FTextureManager::FindSwitch (FTextureID texture) return NULL; } -//========================================================================== -// -// operator<< -// -//========================================================================== - -template<> FArchive &operator<< (FArchive &arc, FSwitchDef* &Switch) -{ - if (arc.IsStoring()) - { - arc << Switch->PreTexture; - } - else - { - FTextureID tex; - arc << tex; - Switch = TexMan.FindSwitch(tex); - } - return arc; -} - diff --git a/src/textures/animations.cpp b/src/textures/animations.cpp index 76ad48443f..74f9b1f557 100644 --- a/src/textures/animations.cpp +++ b/src/textures/animations.cpp @@ -43,7 +43,7 @@ #include "templates.h" #include "w_wad.h" #include "g_level.h" -#include "farchive.h" +#include "serializer.h" // MACROS ------------------------------------------------------------------ @@ -990,17 +990,13 @@ void FTextureManager::UpdateAnimations (DWORD mstime) // //========================================================================== -template<> FArchive &operator<< (FArchive &arc, FDoorAnimation* &Doorani) +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDoorAnimation *&p, FDoorAnimation **def) { - if (arc.IsStoring()) + FTextureID tex = p->BaseTexture; + Serialize(arc, key, tex, def ? &(*def)->BaseTexture : nullptr); + if (arc.isReading()) { - arc << Doorani->BaseTexture; - } - else - { - FTextureID tex; - arc << tex; - Doorani = TexMan.FindAnimatedDoor(tex); + p = TexMan.FindAnimatedDoor(tex); } return arc; } diff --git a/src/textures/pngtexture.cpp b/src/textures/pngtexture.cpp index 9a64bac616..f904d34086 100644 --- a/src/textures/pngtexture.cpp +++ b/src/textures/pngtexture.cpp @@ -66,6 +66,7 @@ protected: FString SourceFile; BYTE *Pixels; Span **Spans; + FileReader *fr; BYTE BitDepth; BYTE ColorType; @@ -209,6 +210,8 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename DWORD len, id; int i; + if (lumpnum == -1) fr = &lump; + UseType = TEX_MiscPatch; LeftOffset = 0; TopOffset = 0; @@ -461,7 +464,7 @@ void FPNGTexture::MakeTexture () } else { - lump = new FileReader(SourceFile.GetChars()); + lump = fr;// new FileReader(SourceFile.GetChars()); } Pixels = new BYTE[Width*Height]; @@ -598,7 +601,7 @@ void FPNGTexture::MakeTexture () delete[] tempix; } } - delete lump; + if (lump != fr) delete lump; } //=========================================================================== @@ -623,7 +626,7 @@ int FPNGTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rotate, FCo } else { - lump = new FileReader(SourceFile.GetChars()); + lump = fr;// new FileReader(SourceFile.GetChars()); } lump->Seek(33, SEEK_SET); @@ -682,7 +685,7 @@ int FPNGTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rotate, FCo lump->Read(&len, 4); lump->Read(&id, 4); M_ReadIDAT (lump, Pixels, Width, Height, pixwidth, BitDepth, ColorType, Interlace, BigLong((unsigned int)len)); - delete lump; + if (lump != fr) delete lump; switch (ColorType) { diff --git a/src/textures/texturemanager.cpp b/src/textures/texturemanager.cpp index 95c976f8ca..3fb01dc6cf 100644 --- a/src/textures/texturemanager.cpp +++ b/src/textures/texturemanager.cpp @@ -40,6 +40,7 @@ #include "templates.h" #include "i_system.h" #include "r_data/r_translate.h" +#include "r_data/sprites.h" #include "c_dispatch.h" #include "v_text.h" #include "sc_man.h" @@ -48,7 +49,6 @@ #include "cmdlib.h" #include "g_level.h" #include "m_fixed.h" -#include "farchive.h" #include "v_video.h" #include "r_renderer.h" #include "r_sky.h" @@ -1074,61 +1074,6 @@ FTextureID FTextureManager::PalCheck(FTextureID tex) return *newtex; } -//========================================================================== -// -// FTextureManager :: WriteTexture -// -//========================================================================== - -void FTextureManager::WriteTexture (FArchive &arc, int picnum) -{ - FTexture *pic; - - if (picnum < 0) - { - arc.WriteName(NULL); - return; - } - else if ((size_t)picnum >= Textures.Size()) - { - pic = Textures[0].Texture; - } - else - { - pic = Textures[picnum].Texture; - } - - if (Wads.GetLinkedTexture(pic->SourceLump) == pic) - { - arc.WriteName(Wads.GetLumpFullName(pic->SourceLump)); - } - else - { - arc.WriteName(pic->Name); - } - arc.WriteCount(pic->UseType); -} - -//========================================================================== -// -// FTextureManager :: ReadTexture -// -//========================================================================== - -int FTextureManager::ReadTexture (FArchive &arc) -{ - int usetype; - const char *name; - - name = arc.ReadName (); - if (name != NULL) - { - usetype = arc.ReadCount (); - return GetTexture (name, usetype).GetIndex(); - } - else return -1; -} - //=========================================================================== // // R_GuesstimateNumTextures @@ -1227,25 +1172,6 @@ int FTextureManager::CountLumpTextures (int lumpnum) } -//========================================================================== -// -// operator<< -// -//========================================================================== - -FArchive &operator<< (FArchive &arc, FTextureID &tex) -{ - if (arc.IsStoring()) - { - TexMan.WriteTexture(arc, tex.texnum); - } - else - { - tex.texnum = TexMan.ReadTexture(arc); - } - return arc; -} - //========================================================================== // // FTextureID::operator+ diff --git a/src/textures/textures.h b/src/textures/textures.h index 6b27376595..58792fc416 100644 --- a/src/textures/textures.h +++ b/src/textures/textures.h @@ -31,7 +31,6 @@ struct FRemapTable; struct FCopyInfo; class FScanner; class PClassInventory; -class FArchive; // Texture IDs class FTextureManager; @@ -45,8 +44,6 @@ public: FNullTextureID() : FTextureID(0) {} }; -FArchive &operator<< (FArchive &arc, FTextureID &tex); - // // Animating textures and planes // @@ -466,9 +463,6 @@ public: int NumTextures () const { return (int)Textures.Size(); } - void WriteTexture (FArchive &arc, int picnum); - int ReadTexture (FArchive &arc); - void UpdateAnimations (DWORD mstime); int GuesstimateNumTextures (); @@ -584,7 +578,6 @@ protected: // A texture that can be drawn to. class DSimpleCanvas; class AActor; -class FArchive; class FCanvasTexture : public FTexture { diff --git a/src/tflags.h b/src/tflags.h index 0c496b228e..a7536ab013 100644 --- a/src/tflags.h +++ b/src/tflags.h @@ -82,13 +82,6 @@ public: TT GetValue() const { return Value; } operator TT() const { return Value; } - // Serialize to FArchive - FArchive& Serialize (FArchive& arc) - { - arc << Value; - return arc; - } - // Set the value of the flagset manually with an integer. // Please think twice before using this. static Self FromInt (TT value) { return Self (static_cast (value)); } @@ -98,6 +91,7 @@ private: template Self operator& (X value) const { return Self::FromInt (Value & value); } template Self operator^ (X value) const { return Self::FromInt (Value ^ value); } +public: // to be removed. TT Value; }; diff --git a/src/v_blend.cpp b/src/v_blend.cpp index b8f3e713d7..e1552105ff 100644 --- a/src/v_blend.cpp +++ b/src/v_blend.cpp @@ -51,7 +51,6 @@ #include "colormatcher.h" #include "v_palette.h" #include "d_player.h" -#include "farchive.h" diff --git a/src/v_font.cpp b/src/v_font.cpp index ef9b69dd12..248568b6fd 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -90,7 +90,6 @@ The FON2 header is followed by variable length data: #include "cmdlib.h" #include "sc_man.h" #include "hu_stuff.h" -#include "farchive.h" #include "textures/textures.h" #include "r_data/r_translate.h" #include "colormatcher.h" @@ -340,33 +339,6 @@ FFont *V_GetFont(const char *name) } return font; } -//========================================================================== -// -// SerializeFFontPtr -// -//========================================================================== - -FArchive &SerializeFFontPtr (FArchive &arc, FFont* &font) -{ - if (arc.IsStoring ()) - { - arc << font->Name; - } - else - { - char *name = NULL; - - arc << name; - font = V_GetFont(name); - if (font == NULL) - { - Printf ("Could not load font %s\n", name); - font = SmallFont; - } - delete[] name; - } - return arc; -} //========================================================================== // diff --git a/src/v_font.h b/src/v_font.h index f6397a0d01..7523e6bf05 100644 --- a/src/v_font.h +++ b/src/v_font.h @@ -39,7 +39,6 @@ class DCanvas; struct FRemapTable; class FTexture; -class FArchive; enum EColorRange { @@ -88,6 +87,7 @@ public: int GetDefaultKerning () const { return GlobalKerning; } virtual void LoadTranslations(); void Preload() const; + const char *GetName() const { return Name; } static FFont *FindFont (const char *fontname); static void StaticPreloadFonts(); @@ -134,8 +134,6 @@ protected: friend void V_ClearFonts(); friend void V_RetranslateFonts(); - - friend FArchive &SerializeFFontPtr (FArchive &arc, FFont* &font); }; diff --git a/src/version.h b/src/version.h index f3706b1de9..a88d3e20b3 100644 --- a/src/version.h +++ b/src/version.h @@ -71,6 +71,9 @@ const char *GetVersionString(); // Note that SAVEVER is not directly comparable to VERSION. // SAVESIG should match SAVEVER. +// extension for savegames +#define SAVEGAME_EXT "zds" + // MINSAVEVER is the minimum level snapshot version that can be loaded. #define MINSAVEVER 4545 @@ -78,10 +81,6 @@ const char *GetVersionString(); // SVN revision ever got. #define SAVEVER 4550 -#define SAVEVERSTRINGIFY2(x) #x -#define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) -#define SAVESIG "ZDOOMSAVE" SAVEVERSTRINGIFY(SAVEVER) - #define DYNLIGHT // This is so that derivates can use the same savegame versions without worrying about engine compatibility diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 52f484afce..f7cd1b9bff 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -797,6 +797,7 @@ void I_FatalError(const char *error, ...) va_start(argptr, error); myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); va_end(argptr); + OutputDebugString(errortext); // Record error to log (if logging) if (Logfile) @@ -832,6 +833,7 @@ void I_Error(const char *error, ...) va_start(argptr, error); myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); va_end(argptr); + OutputDebugString(errortext); throw CRecoverableError(errortext); } diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index b8b805219f..374e2472d1 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -37,6 +37,7 @@ ACTOR Actor native //: Thinker MissileHeight 32 SpriteAngle 0 SpriteRotation 0 + StencilColor "00 00 00" // Functions native bool CheckClass(class checkclass, int ptr_select = AAPTR_DEFAULT, bool match_superclass = false);