- removed the sequential processing of JSON objects because the benefit is too small.

After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.

- do not I_Error out in the serializer unless caused by a programming error.

It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
This commit is contained in:
Christoph Oelckers 2016-09-23 14:04:05 +02:00
parent 5a3f1dcdb6
commit 86e9282193
11 changed files with 364 additions and 272 deletions

View file

@ -931,7 +931,7 @@ void ReadUserInfo(FSerializer &arc, userinfo_t &info, FString &skin)
{ {
while ((key = arc.GetKey())) while ((key = arc.GetKey()))
{ {
arc.StringPtr(key, str); arc.StringPtr(nullptr, str);
name = key; name = key;
cvar = info.CheckKey(name); cvar = info.CheckKey(name);
if (cvar != NULL && *cvar != NULL) if (cvar != NULL && *cvar != NULL)

View file

@ -2250,7 +2250,7 @@ bool PStruct::ReadFields(FSerializer &ar, void *addr) const
} }
else else
{ {
readsomething |= static_cast<const PField *>(sym)->Type->ReadValue(ar, label, readsomething |= static_cast<const PField *>(sym)->Type->ReadValue(ar, nullptr,
(BYTE *)addr + static_cast<const PField *>(sym)->Offset); (BYTE *)addr + static_cast<const PField *>(sym)->Offset);
} }
} }
@ -2543,11 +2543,19 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const
bool readsomething = false; bool readsomething = false;
bool foundsomething = false; bool foundsomething = false;
const char *key; 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;
}
while ((key = ar.GetKey())) while ((key = ar.GetKey()))
{ {
if (strncmp(key, "class:", 6)) if (strncmp(key, "class:", 6))
{ {
// This key does not represent any class fields anymore // We have read all user variable blocks.
break; break;
} }
foundsomething = true; foundsomething = true;
@ -2565,7 +2573,11 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const
} }
if (parent != nullptr) if (parent != nullptr)
{ {
readsomething |= type->ReadFields(ar, addr); if (ar.BeginObject(nullptr))
{
readsomething |= type->ReadFields(ar, addr);
ar.EndObject();
}
} }
else else
{ {

View file

@ -394,8 +394,12 @@ void DThinker::Tick ()
size_t DThinker::PropagateMark() size_t DThinker::PropagateMark()
{ {
assert(NextThinker != NULL && !(NextThinker->ObjectFlags & OF_EuthanizeMe)); // Do not choke on partially initialized objects (as happens when loading a savegame fails)
assert(PrevThinker != NULL && !(PrevThinker->ObjectFlags & OF_EuthanizeMe)); if (NextThinker != nullptr || PrevThinker != nullptr)
{
assert(NextThinker != nullptr && !(NextThinker->ObjectFlags & OF_EuthanizeMe));
assert(PrevThinker != nullptr && !(PrevThinker->ObjectFlags & OF_EuthanizeMe));
}
GC::Mark(NextThinker); GC::Mark(NextThinker);
GC::Mark(PrevThinker); GC::Mark(PrevThinker);
return Super::PropagateMark(); return Super::PropagateMark();

View file

@ -105,6 +105,7 @@ private:
friend struct FThinkerList; friend struct FThinkerList;
friend class FThinkerIterator; friend class FThinkerIterator;
friend class DObject; friend class DObject;
friend class FSerializer;
DThinker *NextThinker, *PrevThinker; DThinker *NextThinker, *PrevThinker;
}; };

View file

@ -27,6 +27,7 @@
#include <stdio.h> #include <stdio.h>
#include <stddef.h> #include <stddef.h>
#include <time.h> #include <time.h>
#include <memory>
#ifdef __APPLE__ #ifdef __APPLE__
#include <CoreServices/CoreServices.h> #include <CoreServices/CoreServices.h>
#endif #endif
@ -1844,154 +1845,162 @@ void G_DoLoadGame ()
Printf ("Could not read savegame '%s'\n", savename.GetChars()); Printf ("Could not read savegame '%s'\n", savename.GetChars());
return; return;
} }
try
FResourceLump *info = resfile->FindLump("info.json");
if (info == nullptr)
{ {
delete resfile; FResourceLump *info = resfile->FindLump("info.json");
Printf ("'%s' is not a valid savegame: Missing 'info.json'.\n", savename.GetChars()); if (info == nullptr)
return;
}
SaveVersion = 0;
void *data = info->CacheLump();
FSerializer arc;
if (!arc.OpenReader((const char *)data, info->LumpSize))
{
Printf("Failed to access savegame info\n");
delete resfile;
return;
}
// 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)
{
// 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"); 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); Printf("Failed to access savegame info\n");
delete resfile;
return;
} }
delete resfile;
return;
}
if (SaveVersion < MINSAVEVER || SaveVersion > SAVEVER) // Check whether this savegame actually has been created by a compatible engine.
{ // Since there are ZDoom derivates using the exact same savegame format but
delete resfile; // with mutual incompatibilities this check simplifies things significantly.
Printf ("Savegame is from an incompatible version"); FString savever, engine, map;
if (SaveVersion < MINSAVEVER) 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;
} }
else
if (SaveVersion < MINSAVEVER || SaveVersion > SAVEVER)
{ {
Printf(": %d (%d is the highest supported)", 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;
} }
Printf("\n");
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; 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);
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;
delete resfile;
// 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();
} }

View file

@ -1773,7 +1773,7 @@ void P_ReadACSDefereds (FSerializer &arc)
{ {
I_Error("Unknown map '%s' in savegame", key); I_Error("Unknown map '%s' in savegame", key);
} }
arc(key, i->deferred); arc(nullptr, i->deferred);
} }
arc.EndObject(); arc.EndObject();
} }

View file

@ -69,6 +69,7 @@ void ASectorAction::Destroy ()
{ {
*prev.act = probe->tracer; *prev.act = probe->tracer;
} }
Sector = nullptr;
} }
Super::Destroy (); Super::Destroy ();

View file

@ -1051,13 +1051,13 @@ static void ReadArrayVars (FSerializer &file, FWorldGlobalArray *vars, size_t co
while ((arraykey = file.GetKey())) while ((arraykey = file.GetKey()))
{ {
int i = (int)strtol(arraykey, nullptr, 10); int i = (int)strtol(arraykey, nullptr, 10);
if (file.BeginObject(arraykey)) if (file.BeginObject(nullptr))
{ {
while ((arraykey = file.GetKey())) while ((arraykey = file.GetKey()))
{ {
int k = (int)strtol(arraykey, nullptr, 10); int k = (int)strtol(arraykey, nullptr, 10);
int val; int val;
file(arraykey, val); file(nullptr, val);
vars[i].Insert(k, val); vars[i].Insert(k, val);
} }
file.EndObject(); file.EndObject();
@ -1612,10 +1612,9 @@ void FBehavior::StaticSerializeModuleStates (FSerializer &arc)
{ {
auto modnum = StaticModules.Size(); auto modnum = StaticModules.Size();
if (arc.BeginObject("acsmodules")) if (arc.BeginArray("acsmodules"))
{ {
arc("count", modnum); int modnum = arc.ArraySize();
if (modnum != StaticModules.Size()) if (modnum != StaticModules.Size())
{ {
I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum); I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum);
@ -1623,35 +1622,31 @@ void FBehavior::StaticSerializeModuleStates (FSerializer &arc)
for (modnum = 0; modnum < StaticModules.Size(); ++modnum) for (modnum = 0; modnum < StaticModules.Size(); ++modnum)
{ {
if (arc.BeginArray("modules")) FBehavior *module = StaticModules[modnum];
const char *modname = module->ModuleName;
int ModSize = module->GetDataSize();
if (arc.BeginObject(nullptr))
{ {
FBehavior *module = StaticModules[modnum]; arc.StringPtr("modname", modname)
const char *modname = module->ModuleName; ("modsize", ModSize);
int ModSize = module->GetDataSize();
if (arc.BeginObject(nullptr)) if (arc.isReading())
{ {
arc.StringPtr("modname", modname) if (stricmp(modname, module->ModuleName) != 0)
("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);
{ }
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())
} {
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);
{
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(); module->SerializeVars(arc);
arc.EndObject();
} }
} }
arc.EndObject(); arc.EndArray();
} }
} }

View file

@ -904,6 +904,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
if (arc.isReading()) if (arc.isReading())
{ {
P_DestroyThinkers(hubload); P_DestroyThinkers(hubload);
interpolator.ClearInterpolations();
arc.ReadObjects(); arc.ReadObjects();
} }
@ -933,15 +934,14 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
sky1texture = level.skytexture1; sky1texture = level.skytexture1;
sky2texture = level.skytexture2; sky2texture = level.skytexture2;
R_InitSkyMap(); R_InitSkyMap();
interpolator.ClearInterpolations(); G_AirControlChanged();
} }
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 // 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); //FBehavior::StaticSerializeModuleStates(arc);
// The order here is important: First world state, then portal state, then thinkers, and last polyobjects. // 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("linedefs", lines, &loadlines[0], numlines);
arc.Array("sidedefs", sides, &loadsides[0], numsides); arc.Array("sidedefs", sides, &loadsides[0], numsides);
@ -950,7 +950,8 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
arc("lineportals", linePortals); arc("lineportals", linePortals);
arc("sectorportals", sectorPortals); arc("sectorportals", sectorPortals);
if (arc.isReading()) P_CollectLinkedPortals(); if (arc.isReading()) P_CollectLinkedPortals();
DThinker::SerializeThinkers(arc, !hubload);
// DThinker::SerializeThinkers(arc, !hubload);
arc.Array("polyobjs", polyobjs, po_NumPolyobjs); arc.Array("polyobjs", polyobjs, po_NumPolyobjs);
arc("subsectors", subsectors); arc("subsectors", subsectors);
StatusBar->SerializeMessages(arc); StatusBar->SerializeMessages(arc);
@ -959,6 +960,7 @@ void G_SerializeLevel(FSerializer &arc, bool hubload)
FCanvasTextureInfo::Serialize(arc); FCanvasTextureInfo::Serialize(arc);
P_SerializePlayers(arc, hubload); P_SerializePlayers(arc, hubload);
P_SerializeSounds(arc); P_SerializeSounds(arc);
if (arc.isReading()) if (arc.isReading())
{ {
for (int i = 0; i < numsectors; i++) for (int i = 0; i < numsectors; i++)

View file

@ -28,6 +28,7 @@
#include "v_font.h" #include "v_font.h"
#include "w_zip.h" #include "w_zip.h"
#include "doomerrors.h" #include "doomerrors.h"
#include "v_text.h"
char nulspace[1024 * 1024 * 4]; char nulspace[1024 * 1024 * 4];
@ -42,12 +43,10 @@ struct FJSONObject
rapidjson::Value *mObject; rapidjson::Value *mObject;
rapidjson::Value::MemberIterator mIterator; rapidjson::Value::MemberIterator mIterator;
int mIndex; int mIndex;
bool mRandomAccess;
FJSONObject(rapidjson::Value *v, bool randomaccess = false) FJSONObject(rapidjson::Value *v)
{ {
mObject = v; mObject = v;
mRandomAccess = randomaccess;
if (v->IsObject()) mIterator = v->MemberBegin(); if (v->IsObject()) mIterator = v->MemberBegin();
else if (v->IsArray()) else if (v->IsArray())
{ {
@ -198,37 +197,34 @@ struct FReader
TArray<FJSONObject> mObjects; TArray<FJSONObject> mObjects;
rapidjson::Document mDoc; rapidjson::Document mDoc;
TArray<DObject *> mDObjects; TArray<DObject *> mDObjects;
rapidjson::Value *mKeyValue = nullptr;
int mPlayers[MAXPLAYERS]; int mPlayers[MAXPLAYERS];
bool mObjectsRead; bool mObjectsRead = false;
FReader(const char *buffer, size_t length, bool randomaccess) FReader(const char *buffer, size_t length)
{ {
rapidjson::Document doc; rapidjson::Document doc;
mDoc.Parse(buffer, length); mDoc.Parse(buffer, length);
mObjects.Push(FJSONObject(&mDoc, randomaccess)); mObjects.Push(FJSONObject(&mDoc));
memset(mPlayers, 0, sizeof(mPlayers)); memset(mPlayers, 0, sizeof(mPlayers));
mObjectsRead = false;
} }
rapidjson::Value *FindKey(const char *key) rapidjson::Value *FindKey(const char *key)
{ {
FJSONObject &obj = mObjects.Last(); FJSONObject &obj = mObjects.Last();
if (obj.mObject->IsObject()) if (obj.mObject->IsObject())
{ {
if (!obj.mRandomAccess) if (key == nullptr)
{ {
if (obj.mIterator != obj.mObject->MemberEnd()) // we are performing an iteration of the object through GetKey.
{ auto p = mKeyValue;
if (!strcmp(key, obj.mIterator->name.GetString())) mKeyValue = nullptr;
{ return p;
return &(obj.mIterator++)->value;
}
}
} }
else else
{ {
// for unordered searches. This is slower but will not rely on sequential order of items. // Find the given key by name;
auto it = obj.mObject->FindMember(key); auto it = obj.mObject->FindMember(key);
if (it == obj.mObject->MemberEnd()) return nullptr; if (it == obj.mObject->MemberEnd()) return nullptr;
return &it->value; return &it->value;
@ -252,8 +248,9 @@ struct FReader
bool FSerializer::OpenWriter(bool pretty) bool FSerializer::OpenWriter(bool pretty)
{ {
if (w != nullptr || r != nullptr) return false; if (w != nullptr || r != nullptr) return false;
w = new FWriter(pretty);
mErrors = 0;
w = new FWriter(pretty);
BeginObject(nullptr); BeginObject(nullptr);
return true; return true;
} }
@ -267,7 +264,9 @@ bool FSerializer::OpenWriter(bool pretty)
bool FSerializer::OpenReader(const char *buffer, size_t length) bool FSerializer::OpenReader(const char *buffer, size_t length)
{ {
if (w != nullptr || r != nullptr) return false; if (w != nullptr || r != nullptr) return false;
r = new FReader(buffer, length, true);
mErrors = 0;
r = new FReader(buffer, length);
return true; return true;
} }
@ -282,15 +281,17 @@ bool FSerializer::OpenReader(FCompressedBuffer *input)
if (input->mSize <= 0 || input->mBuffer == nullptr) return false; if (input->mSize <= 0 || input->mBuffer == nullptr) return false;
if (w != nullptr || r != nullptr) return false; if (w != nullptr || r != nullptr) return false;
mErrors = 0;
if (input->mMethod == METHOD_STORED) if (input->mMethod == METHOD_STORED)
{ {
r = new FReader((char*)input->mBuffer, input->mSize, true); r = new FReader((char*)input->mBuffer, input->mSize);
} }
else else
{ {
char *unpacked = new char[input->mSize]; char *unpacked = new char[input->mSize];
input->Decompress(unpacked); input->Decompress(unpacked);
r = new FReader(unpacked, input->mSize, true); r = new FReader(unpacked, input->mSize);
delete[] unpacked;
} }
return true; return true;
} }
@ -310,9 +311,27 @@ void FSerializer::Close()
} }
if (r != 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<DThinker>(obj);
if (think != nullptr)
{
if (think->NextThinker == nullptr || think->PrevThinker == nullptr)
{
think->Destroy();
}
}
}
delete r; delete r;
r = nullptr; r = nullptr;
} }
if (mErrors > 0)
{
I_Error("%d errors parsing JSON", mErrors);
}
} }
//========================================================================== //==========================================================================
@ -369,7 +388,7 @@ void FSerializer::WriteKey(const char *key)
// //
//========================================================================== //==========================================================================
bool FSerializer::BeginObject(const char *name, bool randomaccess) bool FSerializer::BeginObject(const char *name)
{ {
if (isWriting()) if (isWriting())
{ {
@ -382,13 +401,16 @@ bool FSerializer::BeginObject(const char *name, bool randomaccess)
auto val = r->FindKey(name); auto val = r->FindKey(name);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsObject());
if (val->IsObject()) if (val->IsObject())
{ {
r->mObjects.Push(FJSONObject(val, randomaccess)); r->mObjects.Push(FJSONObject(val));
} }
else else
{ {
I_Error("Object expected for '%s'", name); Printf(TEXTCOLOR_RED "Object expected for '%s'", name);
mErrors++;
return false;
} }
} }
else else
@ -405,7 +427,7 @@ bool FSerializer::BeginObject(const char *name, bool randomaccess)
// //
//========================================================================== //==========================================================================
void FSerializer::EndObject(bool endwarning) void FSerializer::EndObject()
{ {
if (isWriting()) if (isWriting())
{ {
@ -422,14 +444,6 @@ void FSerializer::EndObject(bool endwarning)
} }
else else
{ {
if (endwarning && !r->mObjects.Last().mRandomAccess)
{
if (r->mObjects.Last().mIterator != r->mObjects.Last().mObject->MemberEnd())
{
assert(false && "Incomplete read of sequential object");
I_Error("Incomplete read of sequential object");
}
}
r->mObjects.Pop(); r->mObjects.Pop();
} }
} }
@ -453,13 +467,16 @@ bool FSerializer::BeginArray(const char *name)
auto val = r->FindKey(name); auto val = r->FindKey(name);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsArray());
if (val->IsArray()) if (val->IsArray())
{ {
r->mObjects.Push(FJSONObject(val)); r->mObjects.Push(FJSONObject(val));
} }
else else
{ {
I_Error("Array expected for '%s'", name); Printf(TEXTCOLOR_RED "Array expected for '%s'", name);
mErrors++;
return false;
} }
} }
else else
@ -487,6 +504,7 @@ void FSerializer::EndArray()
} }
else else
{ {
assert(false && "EndArray call not inside an array");
I_Error("EndArray call not inside an array"); I_Error("EndArray call not inside an array");
} }
} }
@ -496,22 +514,6 @@ void FSerializer::EndArray()
} }
} }
//==========================================================================
//
// Discards an entry (only needed for sequential access)
//
//==========================================================================
FSerializer &FSerializer::Discard(const char *key)
{
if (isReading())
{
// just get the key and advance the iterator, if present
if (!r->mObjects.Last().mRandomAccess) r->FindKey(key);
}
return *this;
}
//========================================================================== //==========================================================================
// //
// Special handler for args (because ACS specials' arg0 needs special treatment.) // Special handler for args (because ACS specials' arg0 needs special treatment.)
@ -563,13 +565,17 @@ FSerializer &FSerializer::Args(const char *key, int *args, int *defargs, int spe
} }
else else
{ {
I_Error("Integer expected for '%s[%d]'", key, i); assert(false && "Integer expected");
Printf(TEXTCOLOR_RED "Integer expected for '%s[%d]'", key, i);
mErrors++;
} }
} }
} }
else else
{ {
I_Error("array expected for '%s'", key); assert(false && "array expected");
Printf(TEXTCOLOR_RED "array expected for '%s'", key);
mErrors++;
} }
} }
} }
@ -611,7 +617,9 @@ FSerializer &FSerializer::ScriptNum(const char *key, int &num)
} }
else else
{ {
I_Error("Integer expected for '%s'", key); assert(false && "Integer expected");
Printf(TEXTCOLOR_RED "Integer expected for '%s'", key);
mErrors++;
} }
} }
} }
@ -739,7 +747,8 @@ unsigned FSerializer::GetSize(const char *group)
//========================================================================== //==========================================================================
// //
// // gets the key pointed to by the iterator, caches its value
// and returns the key string.
// //
//========================================================================== //==========================================================================
@ -749,7 +758,8 @@ const char *FSerializer::GetKey()
if (!r->mObjects.Last().mObject->IsObject()) return nullptr; // non-objects do not have keys. if (!r->mObjects.Last().mObject->IsObject()) return nullptr; // non-objects do not have keys.
auto &it = r->mObjects.Last().mIterator; auto &it = r->mObjects.Last().mIterator;
if (it == r->mObjects.Last().mObject->MemberEnd()) return nullptr; if (it == r->mObjects.Last().mObject->MemberEnd()) return nullptr;
return it->name.GetString(); r->mKeyValue = &it->value;
return (it++)->name.GetString();
} }
//========================================================================== //==========================================================================
@ -773,6 +783,9 @@ void FSerializer::WriteObjects()
w->Key("classtype"); w->Key("classtype");
w->String(obj->GetClass()->TypeName.GetChars()); w->String(obj->GetClass()->TypeName.GetChars());
obj->SerializeUserVars(*this);
obj->Serialize(*this);
obj->CheckIfSerialized();
if (obj->IsKindOf(RUNTIME_CLASS(AActor)) && if (obj->IsKindOf(RUNTIME_CLASS(AActor)) &&
(player = static_cast<AActor *>(obj)->player) && (player = static_cast<AActor *>(obj)->player) &&
player->mo == obj) player->mo == obj)
@ -781,9 +794,6 @@ void FSerializer::WriteObjects()
w->Int(int(player - players)); w->Int(int(player - players));
} }
obj->SerializeUserVars(*this);
obj->Serialize(*this);
obj->CheckIfSerialized();
EndObject(); EndObject();
} }
EndArray(); EndArray();
@ -799,8 +809,7 @@ void FSerializer::WriteObjects()
void FSerializer::ReadObjects() void FSerializer::ReadObjects()
{ {
bool founderrors = false; bool founderrors = false;
unsigned i;
if (isReading() && BeginArray("objects")) if (isReading() && BeginArray("objects"))
{ {
// Do not link any thinker that's being created here. This will be done by deserializing the thinker list later. // Do not link any thinker that's being created here. This will be done by deserializing the thinker list later.
@ -845,7 +854,6 @@ void FSerializer::ReadObjects()
if (BeginObject(nullptr)) if (BeginObject(nullptr))
{ {
int pindex = -1; int pindex = -1;
Discard("classtype");
Serialize(*this, "playerindex", pindex, nullptr); Serialize(*this, "playerindex", pindex, nullptr);
if (obj != nullptr) if (obj != nullptr)
{ {
@ -857,20 +865,23 @@ void FSerializer::ReadObjects()
obj->SerializeUserVars(*this); obj->SerializeUserVars(*this);
obj->Serialize(*this); obj->Serialize(*this);
} }
EndObject(true); EndObject();
} }
} }
catch (CRecoverableError &err) catch (CRecoverableError &err)
{ {
I_Error("%s\n while restoring %s", err.GetMessage(),obj? obj->GetClass()->TypeName.GetChars() : "invalid object"); Printf(TEXTCOLOR_RED "'%s'\n while restoring %s", err.GetMessage(),obj? obj->GetClass()->TypeName.GetChars() : "invalid object");
mErrors++;
} }
} }
} }
EndArray(); EndArray();
DThinker::bSerialOverride = false; DThinker::bSerialOverride = false;
assert(!founderrors);
if (founderrors) if (founderrors)
{ {
I_Error("Failed to restore all objects in savegame"); Printf(TEXTCOLOR_RED "Failed to restore all objects in savegame");
mErrors++;
} }
} }
catch(...) catch(...)
@ -989,13 +1000,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *def
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsBool());
if (val->IsBool()) if (val->IsBool())
{ {
value = val->GetBool(); value = val->GetBool();
} }
else else
{ {
I_Error("boolean type expected for '%s'", key); Printf(TEXTCOLOR_RED "boolean type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1023,13 +1036,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, int64_t &value, int64_
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsInt64());
if (val->IsInt64()) if (val->IsInt64())
{ {
value = val->GetInt64(); value = val->GetInt64();
} }
else else
{ {
I_Error("integer type expected for '%s'", key); Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1057,13 +1072,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, uint64_t &value, uint6
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsUint64());
if (val->IsUint64()) if (val->IsUint64())
{ {
value = val->GetUint64(); value = val->GetUint64();
} }
else else
{ {
I_Error("integer type expected for '%s'", key); Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1092,13 +1109,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, int32_t &value, int32_
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsInt());
if (val->IsInt()) if (val->IsInt())
{ {
value = val->GetInt(); value = val->GetInt();
} }
else else
{ {
I_Error("integer type expected for '%s'", key); Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1126,13 +1145,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, uint32_t &value, uint3
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsUint());
if (val->IsUint()) if (val->IsUint())
{ {
value = val->GetUint(); value = val->GetUint();
} }
else else
{ {
I_Error("integer type expected for '%s'", key); Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1202,13 +1223,15 @@ FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsDouble());
if (val->IsDouble()) if (val->IsDouble())
{ {
value = val->GetDouble(); value = val->GetDouble();
} }
else else
{ {
I_Error("float type expected for '%s'", key); Printf(TEXTCOLOR_RED "float type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1239,6 +1262,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *d
template<class T> template<class T>
FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **defval, T *base) 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) if (arc.isReading() || !arc.w->inObject() || defval == nullptr || value != *defval)
{ {
ptrdiff_t vv = value == nullptr ? -1 : value - base; ptrdiff_t vv = value == nullptr ? -1 : value - base;
@ -1342,13 +1366,16 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
{ {
const rapidjson::Value &nameval = (*val)[0]; const rapidjson::Value &nameval = (*val)[0];
const rapidjson::Value &typeval = (*val)[1]; const rapidjson::Value &typeval = (*val)[1];
assert(nameval.IsString() && typeval.IsInt());
if (nameval.IsString() && typeval.IsInt()) if (nameval.IsString() && typeval.IsInt())
{ {
value = TexMan.GetTexture(nameval.GetString(), typeval.GetInt()); value = TexMan.GetTexture(nameval.GetString(), typeval.GetInt());
} }
else else
{ {
I_Error("object does not represent a texture for '%s'", key); Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key);
value.SetNull();
arc.mErrors++;
} }
} }
else if (val->IsNull()) else if (val->IsNull())
@ -1361,7 +1388,10 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
} }
else else
{ {
I_Error("object does not represent a texture for '%s'", key); assert(false && "not a texture");
Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key);
value.SetNull();
arc.mErrors++;
} }
} }
} }
@ -1432,7 +1462,10 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje
} }
else else
{ {
I_Error("Invalid object reference for '%s'", key); assert(false && "invalid object reference");
Printf(TEXTCOLOR_RED "Invalid object reference for '%s'", key);
value = nullptr;
arc.mErrors++;
} }
} }
} }
@ -1469,13 +1502,16 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *d
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsString());
if (val->IsString()) if (val->IsString())
{ {
value = val->GetString(); value = val->GetString();
} }
else else
{ {
I_Error("String expected for '%s'", key); Printf(TEXTCOLOR_RED "String expected for '%s'", key);
arc.mErrors++;
value = NAME_None;
} }
} }
} }
@ -1509,7 +1545,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicCol
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
if (val->IsObject()) if (val->IsArray())
{ {
const rapidjson::Value &colorval = (*val)[0]; const rapidjson::Value &colorval = (*val)[0];
const rapidjson::Value &fadeval = (*val)[1]; const rapidjson::Value &fadeval = (*val)[1];
@ -1517,16 +1553,12 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicCol
if (colorval.IsUint() && fadeval.IsUint() && desatval.IsUint()) if (colorval.IsUint() && fadeval.IsUint() && desatval.IsUint())
{ {
cm = GetSpecialLights(colorval.GetUint(), fadeval.GetUint(), desatval.GetUint()); cm = GetSpecialLights(colorval.GetUint(), fadeval.GetUint(), desatval.GetUint());
} return arc;
else
{
I_Error("object does not represent a colormap for '%s'", key);
} }
} }
else assert(false && "not a colormap");
{ Printf(TEXTCOLOR_RED "object does not represent a colormap for '%s'", key);
I_Error("object does not represent a colormap for '%s'", key); cm = &NormalLight;
}
} }
} }
return arc; return arc;
@ -1555,6 +1587,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsString() || val->IsNull());
if (val->IsString()) if (val->IsString())
{ {
sid = val->GetString(); sid = val->GetString();
@ -1565,7 +1598,9 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI
} }
else else
{ {
I_Error("string type expected for '%s'", key); Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
sid = 0;
arc.mErrors++;
} }
} }
} }
@ -1601,6 +1636,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsString() || val->IsNull());
if (val->IsString()) if (val->IsString())
{ {
clst = PClass::FindActor(val->GetString()); clst = PClass::FindActor(val->GetString());
@ -1611,7 +1647,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor
} }
else else
{ {
I_Error("string type expected for '%s'", key); Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
clst = nullptr;
arc.mErrors++;
} }
} }
} }
@ -1657,7 +1695,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&cl
} }
else else
{ {
I_Error("string type expected for '%s'", key); Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
clst = nullptr;
arc.mErrors++;
} }
} }
} }
@ -1719,18 +1759,33 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState
const rapidjson::Value &ndx = (*val)[1]; const rapidjson::Value &ndx = (*val)[1];
state = nullptr; state = nullptr;
if (cls.IsString() && ndx.IsInt()) assert(cls.IsString() && ndx.IsUint());
if (cls.IsString() && ndx.IsUint())
{ {
PClassActor *clas = PClass::FindActor(cls.GetString()); PClassActor *clas = PClass::FindActor(cls.GetString());
if (clas) if (clas && ndx.GetUint() < (unsigned)clas->NumOwnedStates)
{ {
state = clas->OwnedStates + ndx.GetInt(); 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) else if (!retcode)
{ {
I_Error("array type expected for '%s'", key); assert(false && "not an array");
Printf(TEXTCOLOR_RED "array type expected for '%s'", key);
arc.mErrors++;
} }
} }
} }
@ -1766,6 +1821,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDial
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsUint() || val->IsNull());
if (val->IsNull()) if (val->IsNull())
{ {
node = nullptr; node = nullptr;
@ -1774,7 +1830,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDial
{ {
if (val->GetUint() >= StrifeDialogues.Size()) if (val->GetUint() >= StrifeDialogues.Size())
{ {
node = NULL; node = nullptr;
} }
else else
{ {
@ -1783,7 +1839,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDial
} }
else else
{ {
I_Error("integer expected for '%s'", key); Printf(TEXTCOLOR_RED "integer expected for '%s'", key);
arc.mErrors++;
node = nullptr;
} }
} }
} }
@ -1819,6 +1877,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&p
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsNull() || val->IsString());
if (val->IsNull()) if (val->IsNull())
{ {
pstr = nullptr; pstr = nullptr;
@ -1829,7 +1888,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&p
} }
else else
{ {
I_Error("string expected for '%s'", key); Printf(TEXTCOLOR_RED "string expected for '%s'", key);
pstr = nullptr;
arc.mErrors++;
} }
} }
} }
@ -1858,6 +1919,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsNull() || val->IsString());
if (val->IsNull()) if (val->IsNull())
{ {
pstr = ""; pstr = "";
@ -1868,7 +1930,9 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString
} }
else else
{ {
I_Error("string expected for '%s'", key); Printf(TEXTCOLOR_RED "string expected for '%s'", key);
pstr = "";
arc.mErrors++;
} }
} }
} }
@ -1904,6 +1968,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr
auto val = arc.r->FindKey(key); auto val = arc.r->FindKey(key);
if (val != nullptr) if (val != nullptr)
{ {
assert(val->IsNull() || val->IsString());
if (val->IsNull()) if (val->IsNull())
{ {
pstr = nullptr; pstr = nullptr;
@ -1914,7 +1979,9 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr
} }
else else
{ {
I_Error("string expected for '%s'", key); Printf(TEXTCOLOR_RED "string expected for '%s'", key);
pstr = nullptr;
arc.mErrors++;
} }
} }
} }
@ -1941,7 +2008,7 @@ template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&fon
font = V_GetFont(n); font = V_GetFont(n);
if (font == nullptr) if (font == nullptr)
{ {
Printf("Could not load font %s\n", n); Printf(TEXTCOLOR_ORANGE "Could not load font %s\n", n);
font = SmallFont; font = SmallFont;
} }
return arc; return arc;

View file

@ -73,15 +73,14 @@ public:
bool OpenReader(FCompressedBuffer *input); bool OpenReader(FCompressedBuffer *input);
void Close(); void Close();
void ReadObjects(); void ReadObjects();
bool BeginObject(const char *name, bool randomaccess = false); bool BeginObject(const char *name);
void EndObject(bool endwarning = false); void EndObject();
bool BeginArray(const char *name); bool BeginArray(const char *name);
void EndArray(); void EndArray();
unsigned GetSize(const char *group); unsigned GetSize(const char *group);
const char *GetKey(); const char *GetKey();
const char *GetOutput(unsigned *len = nullptr); const char *GetOutput(unsigned *len = nullptr);
FCompressedBuffer GetCompressedOutput(); FCompressedBuffer GetCompressedOutput();
FSerializer &Discard(const char *key);
FSerializer &Args(const char *key, int *args, int *defargs, int special); FSerializer &Args(const char *key, int *args, int *defargs, int special);
FSerializer &Terrain(const char *key, int &terrain, int *def = nullptr); FSerializer &Terrain(const char *key, int &terrain, int *def = nullptr);
FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def); FSerializer &Sprite(const char *key, int32_t &spritenum, int32_t *def);
@ -167,6 +166,8 @@ public:
obj = (T)val; obj = (T)val;
return *this; return *this;
} }
int mErrors = 0;
}; };
FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval); FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval);