2016-09-23 18:13:22 +00:00
|
|
|
/*
|
|
|
|
** 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.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
#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"
|
2016-09-19 11:36:58 +00:00
|
|
|
#include "po_man.h"
|
2016-09-20 11:21:41 +00:00
|
|
|
#include "v_font.h"
|
2016-09-20 21:13:12 +00:00
|
|
|
#include "w_zip.h"
|
2016-09-22 22:45:41 +00:00
|
|
|
#include "doomerrors.h"
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
#include "v_text.h"
|
2016-10-12 17:22:33 +00:00
|
|
|
#include "cmdlib.h"
|
2016-09-18 23:07:51 +00:00
|
|
|
|
|
|
|
char nulspace[1024 * 1024 * 4];
|
2016-09-23 15:49:33 +00:00
|
|
|
bool save_full = false; // for testing. Should be removed afterward.
|
2016-09-18 23:07:51 +00:00
|
|
|
|
2016-11-01 12:33:18 +00:00
|
|
|
int utf8_encode(int32_t codepoint, char *buffer, int *size)
|
|
|
|
{
|
|
|
|
if (codepoint < 0)
|
|
|
|
return -1;
|
|
|
|
else if (codepoint < 0x80)
|
|
|
|
{
|
|
|
|
buffer[0] = (char)codepoint;
|
|
|
|
*size = 1;
|
|
|
|
}
|
|
|
|
else if (codepoint < 0x800)
|
|
|
|
{
|
|
|
|
buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6);
|
|
|
|
buffer[1] = 0x80 + ((codepoint & 0x03F));
|
|
|
|
*size = 2;
|
|
|
|
}
|
|
|
|
else if (codepoint < 0x10000)
|
|
|
|
{
|
|
|
|
buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12);
|
|
|
|
buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6);
|
|
|
|
buffer[2] = 0x80 + ((codepoint & 0x003F));
|
|
|
|
*size = 3;
|
|
|
|
}
|
|
|
|
else if (codepoint <= 0x10FFFF)
|
|
|
|
{
|
|
|
|
buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18);
|
|
|
|
buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12);
|
|
|
|
buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6);
|
|
|
|
buffer[3] = 0x80 + ((codepoint & 0x00003F));
|
|
|
|
*size = 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int utf8_decode(const char *src, int *size)
|
|
|
|
{
|
|
|
|
int c = src[0] & 255;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
*size = 1;
|
|
|
|
if ((c & 0x80) == 0)
|
|
|
|
{
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
int c1 = src[1] & 255;
|
|
|
|
|
|
|
|
if ((c & 0xE0) == 0xC0)
|
|
|
|
{
|
|
|
|
r = ((c & 0x1F) << 6) | c1;
|
|
|
|
if (r >= 128)
|
|
|
|
{
|
|
|
|
*size = 2;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int c2 = src[2] & 255;
|
|
|
|
|
|
|
|
if ((c & 0xF0) == 0xE0)
|
|
|
|
{
|
|
|
|
r = ((c & 0x0F) << 12) | (c1 << 6) | c2;
|
|
|
|
if (r >= 2048 && (r < 55296 || r > 57343))
|
|
|
|
{
|
|
|
|
*size = 3;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int c3 = src[3] & 255;
|
|
|
|
|
|
|
|
if ((c & 0xF8) == 0xF0)
|
|
|
|
{
|
|
|
|
r = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3;
|
|
|
|
if (r >= 65536 && r <= 1114111)
|
|
|
|
{
|
|
|
|
*size = 4;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static TArray<char> out;
|
|
|
|
static const char *StringToUnicode(const char *cc, int size = -1)
|
|
|
|
{
|
|
|
|
int ch;
|
|
|
|
const char *c = cc;
|
|
|
|
int count = 0;
|
|
|
|
int count1 = 0;
|
|
|
|
out.Clear();
|
2016-11-12 10:18:17 +00:00
|
|
|
while ((ch = (*c++) & 255))
|
2016-11-01 12:33:18 +00:00
|
|
|
{
|
|
|
|
count1++;
|
|
|
|
if (ch >= 128)
|
|
|
|
{
|
|
|
|
if (ch < 0x800) count += 2;
|
|
|
|
else count += 3;
|
|
|
|
// The source cannot contain 4-byte chars.
|
|
|
|
}
|
|
|
|
else count++;
|
|
|
|
if (count1 == size && size > 0) break;
|
|
|
|
}
|
|
|
|
if (count == count1) return cc; // string is pure ASCII.
|
|
|
|
// we need to convert
|
|
|
|
out.Resize(count + 1);
|
|
|
|
out.Last() = 0;
|
|
|
|
c = cc;
|
|
|
|
int i = 0;
|
2016-11-12 10:18:17 +00:00
|
|
|
while ((ch = (*c++) & 255))
|
2016-11-01 12:33:18 +00:00
|
|
|
{
|
|
|
|
utf8_encode(ch, &out[i], &count1);
|
|
|
|
i += count1;
|
|
|
|
}
|
|
|
|
return &out[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *UnicodeToString(const char *cc)
|
|
|
|
{
|
|
|
|
out.Resize((unsigned)strlen(cc) + 1);
|
|
|
|
int ndx = 0;
|
|
|
|
while (*cc != 0)
|
|
|
|
{
|
|
|
|
int size;
|
|
|
|
int c = utf8_decode(cc, &size);
|
|
|
|
if (c < 0 || c > 255) c = '?';
|
|
|
|
out[ndx++] = c;
|
|
|
|
cc += size;
|
|
|
|
}
|
|
|
|
out[ndx] = 0;
|
|
|
|
return &out[0];
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
struct FJSONObject
|
|
|
|
{
|
|
|
|
rapidjson::Value *mObject;
|
|
|
|
rapidjson::Value::MemberIterator mIterator;
|
|
|
|
int mIndex;
|
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
FJSONObject(rapidjson::Value *v)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
mObject = v;
|
|
|
|
if (v->IsObject()) mIterator = v->MemberBegin();
|
2016-09-20 16:27:47 +00:00
|
|
|
else if (v->IsArray())
|
|
|
|
{
|
|
|
|
mIndex = 0;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// some wrapper stuff to keep the RapidJSON dependencies out of the global headers.
|
|
|
|
// FSerializer should not expose any of this.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
struct FWriter
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
typedef rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<> > Writer;
|
|
|
|
typedef rapidjson::PrettyWriter<rapidjson::StringBuffer, rapidjson::UTF8<> > PrettyWriter;
|
2016-09-18 23:07:51 +00:00
|
|
|
|
2016-09-21 19:57:24 +00:00
|
|
|
Writer *mWriter1;
|
|
|
|
PrettyWriter *mWriter2;
|
2016-09-18 23:07:51 +00:00
|
|
|
TArray<bool> mInObject;
|
|
|
|
rapidjson::StringBuffer mOutString;
|
|
|
|
TArray<DObject *> mDObjects;
|
|
|
|
TMap<DObject *, int> mObjectMap;
|
|
|
|
|
2016-09-21 19:57:24 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:48:48 +00:00
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
bool inObject() const
|
|
|
|
{
|
2016-09-18 23:48:48 +00:00
|
|
|
return mInObject.Size() > 0 && mInObject.Last();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-21 19:57:24 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
k = StringToUnicode(k);
|
2016-09-21 19:57:24 +00:00
|
|
|
if (mWriter1) mWriter1->String(k);
|
|
|
|
else if (mWriter2) mWriter2->String(k);
|
|
|
|
}
|
|
|
|
|
|
|
|
void String(const char *k, int size)
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
k = StringToUnicode(k, size);
|
|
|
|
if (mWriter1) mWriter1->String(k);
|
|
|
|
else if (mWriter2) mWriter2->String(k);
|
2016-09-21 19:57:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2016-09-28 09:58:12 +00:00
|
|
|
if (mWriter1)
|
|
|
|
{
|
2016-10-02 16:50:37 +00:00
|
|
|
mWriter1->Double(k);
|
2016-09-28 09:58:12 +00:00
|
|
|
}
|
|
|
|
else if (mWriter2)
|
|
|
|
{
|
2016-10-02 16:50:37 +00:00
|
|
|
mWriter2->Double(k);
|
2016-09-28 09:58:12 +00:00
|
|
|
}
|
2016-09-21 19:57:24 +00:00
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
struct FReader
|
|
|
|
{
|
|
|
|
TArray<FJSONObject> mObjects;
|
2016-09-21 19:57:24 +00:00
|
|
|
rapidjson::Document mDoc;
|
2016-09-22 22:45:41 +00:00
|
|
|
TArray<DObject *> mDObjects;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
rapidjson::Value *mKeyValue = nullptr;
|
2016-09-22 22:45:41 +00:00
|
|
|
int mPlayers[MAXPLAYERS];
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
bool mObjectsRead = false;
|
2016-09-18 23:07:51 +00:00
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
FReader(const char *buffer, size_t length)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
rapidjson::Document doc;
|
2016-09-21 19:57:24 +00:00
|
|
|
mDoc.Parse(buffer, length);
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
mObjects.Push(FJSONObject(&mDoc));
|
2016-09-23 15:49:33 +00:00
|
|
|
memset(mPlayers, -1, sizeof(mPlayers));
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
rapidjson::Value *FindKey(const char *key)
|
|
|
|
{
|
|
|
|
FJSONObject &obj = mObjects.Last();
|
|
|
|
|
|
|
|
if (obj.mObject->IsObject())
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
if (key == nullptr)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
// we are performing an iteration of the object through GetKey.
|
|
|
|
auto p = mKeyValue;
|
|
|
|
mKeyValue = nullptr;
|
|
|
|
return p;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
// Find the given key by name;
|
2016-09-20 16:27:47 +00:00
|
|
|
auto it = obj.mObject->FindMember(key);
|
|
|
|
if (it == obj.mObject->MemberEnd()) return nullptr;
|
|
|
|
return &it->value;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-20 21:13:12 +00:00
|
|
|
else if (obj.mObject->IsArray() && (unsigned)obj.mIndex < obj.mObject->Size())
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-20 17:45:32 +00:00
|
|
|
return &(*obj.mObject)[obj.mIndex++];
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-21 19:57:24 +00:00
|
|
|
bool FSerializer::OpenWriter(bool pretty)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-18 23:48:48 +00:00
|
|
|
if (w != nullptr || r != nullptr) return false;
|
2016-09-18 23:07:51 +00:00
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
mErrors = 0;
|
|
|
|
w = new FWriter(pretty);
|
2016-09-21 19:57:24 +00:00
|
|
|
BeginObject(nullptr);
|
2016-09-18 23:07:51 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
bool FSerializer::OpenReader(const char *buffer, size_t length)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-18 23:48:48 +00:00
|
|
|
if (w != nullptr || r != nullptr) return false;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
|
|
|
|
mErrors = 0;
|
|
|
|
r = new FReader(buffer, length);
|
2016-09-18 23:07:51 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
bool FSerializer::OpenReader(FCompressedBuffer *input)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-20 21:13:12 +00:00
|
|
|
if (input->mSize <= 0 || input->mBuffer == nullptr) return false;
|
|
|
|
if (w != nullptr || r != nullptr) return false;
|
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
mErrors = 0;
|
2016-09-20 21:13:12 +00:00
|
|
|
if (input->mMethod == METHOD_STORED)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
r = new FReader((char*)input->mBuffer, input->mSize);
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char *unpacked = new char[input->mSize];
|
|
|
|
input->Decompress(unpacked);
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
r = new FReader(unpacked, input->mSize);
|
|
|
|
delete[] unpacked;
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
2016-09-22 22:45:41 +00:00
|
|
|
return true;
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void FSerializer::Close()
|
|
|
|
{
|
2016-09-18 23:07:51 +00:00
|
|
|
if (w != nullptr)
|
|
|
|
{
|
|
|
|
delete w;
|
|
|
|
w = nullptr;
|
|
|
|
}
|
|
|
|
if (r != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
delete r;
|
|
|
|
r = nullptr;
|
|
|
|
}
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
if (mErrors > 0)
|
|
|
|
{
|
|
|
|
I_Error("%d errors parsing JSON", mErrors);
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-10-17 04:19:08 +00:00
|
|
|
unsigned FSerializer::ArraySize()
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
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())
|
|
|
|
{
|
2016-09-20 11:21:41 +00:00
|
|
|
assert(key != nullptr);
|
2016-09-18 23:07:51 +00:00
|
|
|
if (key == nullptr)
|
|
|
|
{
|
|
|
|
I_Error("missing element name");
|
|
|
|
}
|
2016-09-21 19:57:24 +00:00
|
|
|
w->Key(key);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
bool FSerializer::BeginObject(const char *name)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
WriteKey(name);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->StartObject();
|
2016-09-18 23:07:51 +00:00
|
|
|
w->mInObject.Push(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(name);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsObject());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsObject())
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
r->mObjects.Push(FJSONObject(val));
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "Object expected for '%s'", name);
|
|
|
|
mErrors++;
|
|
|
|
return false;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
void FSerializer::EndObject()
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
if (w->inObject())
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->EndObject();
|
2016-09-18 23:07:51 +00:00
|
|
|
w->mInObject.Pop();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
assert(false && "EndObject call not inside an object");
|
2016-09-18 23:07:51 +00:00
|
|
|
I_Error("EndObject call not inside an object");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r->mObjects.Pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool FSerializer::BeginArray(const char *name)
|
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
WriteKey(name);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->StartArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
w->mInObject.Push(false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(name);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsArray());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsArray())
|
|
|
|
{
|
|
|
|
r->mObjects.Push(FJSONObject(val));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "Array expected for '%s'", name);
|
|
|
|
mErrors++;
|
|
|
|
return false;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void FSerializer::EndArray()
|
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
2016-09-18 23:48:48 +00:00
|
|
|
if (!w->inObject())
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->EndArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
w->mInObject.Pop();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "EndArray call not inside an array");
|
2016-09-18 23:07:51 +00:00
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->StartArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
|
{
|
|
|
|
if (i == 0 && args[i] < 0 && P_IsACSSpecial(special))
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->String(FName(ENamedName(-args[i])).GetChars());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->Int(args[i]);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-21 19:57:24 +00:00
|
|
|
w->EndArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsArray())
|
|
|
|
{
|
|
|
|
unsigned int cnt = MIN<unsigned>(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())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
args[i] = -FName(UnicodeToString(aval.GetString()));
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "Integer expected");
|
|
|
|
Printf(TEXTCOLOR_RED "Integer expected for '%s[%d]'", key, i);
|
|
|
|
mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "array expected");
|
|
|
|
Printf(TEXTCOLOR_RED "array expected for '%s'", key);
|
|
|
|
mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2016-09-19 22:41:22 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Special handler for script numbers
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &FSerializer::ScriptNum(const char *key, int &num)
|
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
WriteKey(key);
|
|
|
|
if (num < 0)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->String(FName(ENamedName(-num)).GetChars());
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
w->Int(num);
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsInt())
|
|
|
|
{
|
|
|
|
num = val->GetInt();
|
|
|
|
}
|
|
|
|
else if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
num = -FName(UnicodeToString(val->GetString()));
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "Integer expected");
|
|
|
|
Printf(TEXTCOLOR_RED "Integer expected for '%s'", key);
|
|
|
|
mErrors++;
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-19 10:53:42 +00:00
|
|
|
FSerializer &FSerializer::Sprite(const char *key, int32_t &spritenum, int32_t *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
if (w->inObject() && def != nullptr && *def == spritenum) return *this;
|
|
|
|
WriteKey(key);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->String(sprites[spritenum].name, 4);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
uint32_t name = *reinterpret_cast<const uint32_t*>(UnicodeToString(val->GetString()));
|
2016-09-18 23:07:51 +00:00
|
|
|
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);
|
2016-09-23 18:05:12 +00:00
|
|
|
if (charptr != nullptr)
|
|
|
|
w->String(charptr);
|
|
|
|
else
|
|
|
|
w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
charptr = UnicodeToString(val->GetString());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
charptr = nullptr;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2016-09-20 07:11:13 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-21 10:19:13 +00:00
|
|
|
FSerializer &FSerializer::AddString(const char *key, const char *charptr)
|
|
|
|
{
|
|
|
|
if (isWriting())
|
|
|
|
{
|
|
|
|
WriteKey(key);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->String(charptr);
|
2016-09-21 10:19:13 +00:00
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-20 07:11:13 +00:00
|
|
|
unsigned FSerializer::GetSize(const char *group)
|
|
|
|
{
|
|
|
|
if (isWriting()) return -1; // we do not know this when writing.
|
|
|
|
|
2016-09-21 19:57:24 +00:00
|
|
|
const rapidjson::Value &val = r->mDoc[group];
|
2016-09-20 07:11:13 +00:00
|
|
|
if (!val.IsArray()) return -1;
|
|
|
|
return val.Size();
|
|
|
|
}
|
|
|
|
|
2016-09-20 16:27:47 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
// gets the key pointed to by the iterator, caches its value
|
|
|
|
// and returns the key string.
|
2016-09-20 16:27:47 +00:00
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
const char *FSerializer::GetKey()
|
|
|
|
{
|
|
|
|
if (isWriting()) return nullptr; // we do not know this when writing.
|
2016-09-22 17:36:23 +00:00
|
|
|
if (!r->mObjects.Last().mObject->IsObject()) return nullptr; // non-objects do not have keys.
|
2016-09-20 16:27:47 +00:00
|
|
|
auto &it = r->mObjects.Last().mIterator;
|
|
|
|
if (it == r->mObjects.Last().mObject->MemberEnd()) return nullptr;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
r->mKeyValue = &it->value;
|
|
|
|
return (it++)->name.GetString();
|
2016-09-20 16:27:47 +00:00
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Writes out all collected objects
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void FSerializer::WriteObjects()
|
|
|
|
{
|
2016-09-21 22:18:31 +00:00
|
|
|
if (isWriting() && w->mDObjects.Size())
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
BeginArray("objects");
|
2016-09-20 08:59:48 +00:00
|
|
|
// we cannot use the C++11 shorthand syntax here because the array may grow while being processed.
|
2016-09-18 23:07:51 +00:00
|
|
|
for (unsigned i = 0; i < w->mDObjects.Size(); i++)
|
|
|
|
{
|
2016-09-20 08:59:48 +00:00
|
|
|
auto obj = w->mDObjects[i];
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
BeginObject(nullptr);
|
2016-09-21 19:57:24 +00:00
|
|
|
w->Key("classtype");
|
|
|
|
w->String(obj->GetClass()->TypeName.GetChars());
|
2016-09-20 08:59:48 +00:00
|
|
|
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
obj->SerializeUserVars(*this);
|
|
|
|
obj->Serialize(*this);
|
|
|
|
obj->CheckIfSerialized();
|
2016-09-18 23:07:51 +00:00
|
|
|
EndObject();
|
|
|
|
}
|
|
|
|
EndArray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Writes out all collected objects
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-23 15:49:33 +00:00
|
|
|
void FSerializer::ReadObjects(bool hubtravel)
|
2016-09-22 22:45:41 +00:00
|
|
|
{
|
|
|
|
bool founderrors = false;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
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());
|
2016-09-23 16:12:38 +00:00
|
|
|
// First iteration: create all the objects but do nothing with them yet.
|
2016-09-22 22:45:41 +00:00
|
|
|
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.
|
2016-09-23 15:49:33 +00:00
|
|
|
int pindex = -1;
|
2016-09-22 22:45:41 +00:00
|
|
|
|
|
|
|
Serialize(*this, "classtype", clsname, nullptr);
|
|
|
|
PClass *cls = PClass::FindClass(clsname);
|
|
|
|
if (cls == nullptr)
|
|
|
|
{
|
2016-09-24 07:00:31 +00:00
|
|
|
Printf("Unknown object class '%s' in savegame", clsname.GetChars());
|
2016-09-22 22:45:41 +00:00
|
|
|
founderrors = true;
|
2016-09-23 15:49:33 +00:00
|
|
|
r->mDObjects[i] = RUNTIME_CLASS(AActor)->CreateNew(); // make sure we got at least a valid pointer for the duration of the loading process.
|
2016-09-23 16:12:38 +00:00
|
|
|
r->mDObjects[i]->Destroy(); // but we do not want to keep this around, so destroy it right away.
|
2016-09-22 22:45:41 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r->mDObjects[i] = cls->CreateNew();
|
|
|
|
}
|
|
|
|
EndObject();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now that everything has been created and we can retrieve the pointers we can deserialize it.
|
2016-09-23 07:38:55 +00:00
|
|
|
r->mObjectsRead = true;
|
2016-09-22 22:45:41 +00:00
|
|
|
|
|
|
|
if (!founderrors)
|
|
|
|
{
|
|
|
|
// Reset to start;
|
|
|
|
r->mObjects.Last().mIndex = 0;
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < r->mDObjects.Size(); i++)
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
auto obj = r->mDObjects[i];
|
2016-09-23 15:49:33 +00:00
|
|
|
if (BeginObject(nullptr))
|
2016-09-22 22:45:41 +00:00
|
|
|
{
|
2016-09-23 15:49:33 +00:00
|
|
|
if (obj != nullptr)
|
2016-09-22 22:45:41 +00:00
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
int pindex = -1;
|
2016-09-23 15:49:33 +00:00
|
|
|
try
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
obj->SerializeUserVars(*this);
|
|
|
|
obj->Serialize(*this);
|
|
|
|
}
|
2016-09-23 15:49:33 +00:00
|
|
|
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++;
|
|
|
|
}
|
2016-09-22 22:45:41 +00:00
|
|
|
}
|
2016-09-23 15:49:33 +00:00
|
|
|
EndObject();
|
2016-09-23 07:38:55 +00:00
|
|
|
}
|
2016-09-23 15:49:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
EndArray();
|
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
DThinker::bSerialOverride = false;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(!founderrors);
|
2016-09-23 07:38:55 +00:00
|
|
|
if (founderrors)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "Failed to restore all objects in savegame");
|
|
|
|
mErrors++;
|
2016-09-23 07:38:55 +00:00
|
|
|
}
|
2016-09-22 22:45:41 +00:00
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
// nuke all objects we created here.
|
|
|
|
for (auto obj : r->mDObjects)
|
|
|
|
{
|
|
|
|
obj->Destroy();
|
|
|
|
}
|
|
|
|
r->mDObjects.Clear();
|
|
|
|
|
2016-09-22 22:45:41 +00:00
|
|
|
// make sure this flag gets unset, even if something in here throws an error.
|
|
|
|
DThinker::bSerialOverride = false;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
const char *FSerializer::GetOutput(unsigned *len)
|
|
|
|
{
|
2016-09-20 21:13:12 +00:00
|
|
|
if (isReading()) return nullptr;
|
2016-09-21 22:18:31 +00:00
|
|
|
WriteObjects();
|
2016-09-21 10:19:13 +00:00
|
|
|
EndObject();
|
2016-09-18 23:07:51 +00:00
|
|
|
if (len != nullptr)
|
|
|
|
{
|
|
|
|
*len = (unsigned)w->mOutString.GetSize();
|
|
|
|
}
|
|
|
|
return w->mOutString.GetString();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
FCompressedBuffer FSerializer::GetCompressedOutput()
|
|
|
|
{
|
2016-09-21 15:37:56 +00:00
|
|
|
if (isReading()) return{ 0,0,0,0,0,nullptr };
|
2016-09-20 21:13:12 +00:00
|
|
|
FCompressedBuffer buff;
|
2016-09-21 22:18:31 +00:00
|
|
|
WriteObjects();
|
2016-09-21 10:19:13 +00:00
|
|
|
EndObject();
|
2016-09-20 21:13:12 +00:00
|
|
|
buff.mSize = (unsigned)w->mOutString.GetSize();
|
|
|
|
buff.mZipFlags = 0;
|
2016-09-21 15:37:56 +00:00
|
|
|
buff.mCRC32 = crc32(0, (const Bytef*)w->mOutString.GetString(), buff.mSize);
|
2016-09-20 21:13:12 +00:00
|
|
|
|
|
|
|
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;
|
2016-09-21 15:37:56 +00:00
|
|
|
return buff;
|
2016-09-20 21:13:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
error:
|
|
|
|
memcpy(compressbuf, w->mOutString.GetString(), buff.mSize + 1);
|
|
|
|
buff.mCompressedSize = buff.mSize;
|
|
|
|
buff.mMethod = METHOD_STORED;
|
|
|
|
return buff;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Bool(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsBool());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsBool())
|
|
|
|
{
|
|
|
|
value = val->GetBool();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "boolean type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Int64(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsInt64());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsInt64())
|
|
|
|
{
|
|
|
|
value = val->GetInt64();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Uint64(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsUint64());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsUint64())
|
|
|
|
{
|
|
|
|
value = val->GetUint64();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Int(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsInt());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsInt())
|
|
|
|
{
|
|
|
|
value = val->GetInt();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Uint(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsUint());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsUint())
|
|
|
|
{
|
|
|
|
value = val->GetUint();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "integer type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Double(value);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsDouble());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsDouble())
|
|
|
|
{
|
|
|
|
value = val->GetDouble();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "float type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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<class T>
|
|
|
|
FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **defval, T *base)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(base != nullptr);
|
2016-09-18 23:07:51 +00:00
|
|
|
if (arc.isReading() || !arc.w->inObject() || defval == nullptr || value != *defval)
|
|
|
|
{
|
2016-09-24 10:15:45 +00:00
|
|
|
int64_t vv = value == nullptr ? -1 : value - base;
|
2016-09-18 23:07:51 +00:00
|
|
|
Serialize(arc, key, vv, nullptr);
|
|
|
|
value = vv < 0 ? nullptr : base + vv;
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval)
|
2016-09-19 11:36:58 +00:00
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, polyobjs);
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval)
|
|
|
|
{
|
|
|
|
return SerializePointer<const FPolyObj>(arc, key, value, defval, polyobjs);
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, sides);
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, sectors);
|
|
|
|
}
|
|
|
|
|
2016-09-20 21:13:12 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval)
|
|
|
|
{
|
|
|
|
return SerializePointer<const sector_t>(arc, key, value, defval, sectors);
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, players);
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, lines);
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, vertex_t *&value, vertex_t **defval)
|
|
|
|
{
|
|
|
|
return SerializePointer(arc, key, value, defval, vertexes);
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
if (value.isNull())
|
|
|
|
{
|
|
|
|
// save 'no texture' in a more space saving way
|
|
|
|
arc.WriteKey(key);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Int(0);
|
2016-09-18 23:07:51 +00:00
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->StartArray();
|
|
|
|
arc.w->String(name);
|
|
|
|
arc.w->Int(pic->UseType);
|
|
|
|
arc.w->EndArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
2016-09-22 22:45:41 +00:00
|
|
|
if (val->IsArray())
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
const rapidjson::Value &nameval = (*val)[0];
|
|
|
|
const rapidjson::Value &typeval = (*val)[1];
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(nameval.IsString() && typeval.IsInt());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (nameval.IsString() && typeval.IsInt())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
value = TexMan.GetTexture(UnicodeToString(nameval.GetString()), typeval.GetInt());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key);
|
|
|
|
value.SetNull();
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (val->IsNull())
|
|
|
|
{
|
|
|
|
value.SetInvalid();
|
|
|
|
}
|
|
|
|
else if (val->IsInt() && val->GetInt() == 0)
|
|
|
|
{
|
|
|
|
value.SetNull();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "not a texture");
|
|
|
|
Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'", key);
|
|
|
|
value.SetNull();
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// This never uses defval and instead uses 'null' as default
|
|
|
|
// because object pointers cannot be safely defaulted to anything else.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-20 16:27:47 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-20 16:27:47 +00:00
|
|
|
if (retcode) *retcode = true;
|
2016-09-18 23:07:51 +00:00
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
if (value != nullptr)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
int ndx;
|
2016-09-20 23:18:29 +00:00
|
|
|
if (value == WP_NOCHANGE)
|
|
|
|
{
|
|
|
|
ndx = -1;
|
|
|
|
}
|
2016-10-16 01:57:56 +00:00
|
|
|
else if (value->ObjectFlags & (OF_EuthanizeMe | OF_Transient))
|
2016-09-23 07:38:55 +00:00
|
|
|
{
|
|
|
|
return arc;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
else
|
|
|
|
{
|
2016-09-20 23:18:29 +00:00
|
|
|
int *pndx = arc.w->mObjectMap.CheckKey(value);
|
|
|
|
if (pndx != nullptr)
|
|
|
|
{
|
|
|
|
ndx = *pndx;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ndx = arc.w->mDObjects.Push(value);
|
|
|
|
arc.w->mObjectMap[value] = ndx;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
Serialize(arc, key, ndx, nullptr);
|
|
|
|
}
|
2016-09-23 19:24:56 +00:00
|
|
|
else if (!arc.w->inObject())
|
|
|
|
{
|
|
|
|
arc.w->Null();
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-23 07:38:55 +00:00
|
|
|
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");
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
auto val = arc.r->FindKey(key);
|
2016-09-23 19:24:56 +00:00
|
|
|
if (val != nullptr)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
if (val->IsNull())
|
2016-09-20 23:18:29 +00:00
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
value = nullptr;
|
|
|
|
return arc;
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
2016-09-23 19:24:56 +00:00
|
|
|
else if (val->IsInt())
|
2016-09-20 23:18:29 +00:00
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
int index = val->GetInt();
|
|
|
|
if (index == -1)
|
2016-09-23 07:38:55 +00:00
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
value = WP_NOCHANGE;
|
2016-09-23 07:38:55 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-23 19:24:56 +00:00
|
|
|
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;
|
|
|
|
}
|
2016-09-23 07:38:55 +00:00
|
|
|
}
|
2016-09-23 19:24:56 +00:00
|
|
|
return arc;
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-23 19:24:56 +00:00
|
|
|
if (!retcode)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
value = nullptr;
|
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
*retcode = false;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(value.GetChars());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsString());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
value = UnicodeToString(val->GetString());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "String expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
|
|
|
value = NAME_None;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicColormap *&cm, FDynamicColormap **def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->StartArray();
|
|
|
|
arc.w->Uint(cm->Color);
|
|
|
|
arc.w->Uint(cm->Fade);
|
|
|
|
arc.w->Uint(cm->Desaturate);
|
|
|
|
arc.w->EndArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
if (val->IsArray())
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
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());
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
return arc;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "not a colormap");
|
|
|
|
Printf(TEXTCOLOR_RED "object does not represent a colormap for '%s'", key);
|
|
|
|
cm = &NormalLight;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def)
|
|
|
|
{
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-19 22:41:22 +00:00
|
|
|
if (!arc.w->inObject() || def == nullptr || sid != *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
arc.WriteKey(key);
|
|
|
|
const char *sn = (const char*)sid;
|
2016-09-21 19:57:24 +00:00
|
|
|
if (sn != nullptr) arc.w->String(sn);
|
|
|
|
else arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsString() || val->IsNull());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
sid = UnicodeToString(val->GetString());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else if (val->IsNull())
|
|
|
|
{
|
|
|
|
sid = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
|
|
|
|
sid = 0;
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor *&clst, PClassActor **def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-19 22:41:22 +00:00
|
|
|
if (!arc.w->inObject() || def == nullptr || clst != *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
arc.WriteKey(key);
|
2016-09-20 23:18:29 +00:00
|
|
|
if (clst == nullptr)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(clst->TypeName.GetChars());
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsString() || val->IsNull());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
clst = PClass::FindActor(UnicodeToString(val->GetString()));
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-20 23:18:29 +00:00
|
|
|
else if (val->IsNull())
|
|
|
|
{
|
|
|
|
clst = nullptr;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
|
|
|
|
clst = nullptr;
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-20 16:27:47 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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);
|
2016-09-20 23:18:29 +00:00
|
|
|
if (clst == nullptr)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(clst->TypeName.GetChars());
|
2016-09-20 23:18:29 +00:00
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
clst = PClass::FindClass(UnicodeToString(val->GetString()));
|
2016-09-20 16:27:47 +00:00
|
|
|
}
|
2016-09-20 23:18:29 +00:00
|
|
|
else if (val->IsNull())
|
|
|
|
{
|
|
|
|
clst = nullptr;
|
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string type expected for '%s'", key);
|
|
|
|
clst = nullptr;
|
|
|
|
arc.mErrors++;
|
2016-09-20 16:27:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-18 23:07:51 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-20 16:27:47 +00:00
|
|
|
FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def, bool *retcode)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-20 16:27:47 +00:00
|
|
|
if (retcode) *retcode = false;
|
2016-09-18 23:07:51 +00:00
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-19 22:41:22 +00:00
|
|
|
if (!arc.w->inObject() || def == nullptr || state != *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
2016-09-20 16:27:47 +00:00
|
|
|
if (retcode) *retcode = true;
|
2016-09-18 23:07:51 +00:00
|
|
|
arc.WriteKey(key);
|
|
|
|
if (state == nullptr)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PClassActor *info = FState::StaticFindStateOwner(state);
|
|
|
|
|
|
|
|
if (info != NULL)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->StartArray();
|
|
|
|
arc.w->String(info->TypeName.GetChars());
|
|
|
|
arc.w->Uint((uint32_t)(state - info->OwnedStates));
|
|
|
|
arc.w->EndArray();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
|
|
|
if (val->IsNull())
|
|
|
|
{
|
2016-09-20 16:27:47 +00:00
|
|
|
if (retcode) *retcode = true;
|
2016-09-18 23:07:51 +00:00
|
|
|
state = nullptr;
|
|
|
|
}
|
|
|
|
else if (val->IsArray())
|
|
|
|
{
|
2016-09-20 16:27:47 +00:00
|
|
|
if (retcode) *retcode = true;
|
2016-09-19 08:34:54 +00:00
|
|
|
const rapidjson::Value &cls = (*val)[0];
|
|
|
|
const rapidjson::Value &ndx = (*val)[1];
|
|
|
|
|
|
|
|
state = nullptr;
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(cls.IsString() && ndx.IsUint());
|
|
|
|
if (cls.IsString() && ndx.IsUint())
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
PClassActor *clas = PClass::FindActor(UnicodeToString(cls.GetString()));
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
if (clas && ndx.GetUint() < (unsigned)clas->NumOwnedStates)
|
|
|
|
{
|
|
|
|
state = clas->OwnedStates + ndx.GetUint();
|
|
|
|
}
|
|
|
|
else
|
2016-09-19 08:34:54 +00:00
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
// 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);
|
2016-09-19 08:34:54 +00:00
|
|
|
}
|
|
|
|
}
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
assert(false && "not a state");
|
|
|
|
Printf(TEXTCOLOR_RED "data does not represent a state for '%s'", key);
|
|
|
|
arc.mErrors++;
|
|
|
|
}
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
2016-09-20 16:27:47 +00:00
|
|
|
else if (!retcode)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(false && "not an array");
|
|
|
|
Printf(TEXTCOLOR_RED "array type expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDialogueNode *&node, FStrifeDialogueNode **def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-19 22:41:22 +00:00
|
|
|
if (!arc.w->inObject() || def == nullptr || node != *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
arc.WriteKey(key);
|
|
|
|
if (node == nullptr)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Uint(node->ThisNodeNum);
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsUint() || val->IsNull());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsNull())
|
|
|
|
{
|
|
|
|
node = nullptr;
|
|
|
|
}
|
|
|
|
else if (val->IsUint())
|
|
|
|
{
|
|
|
|
if (val->GetUint() >= StrifeDialogues.Size())
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
node = nullptr;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
node = StrifeDialogues[val->GetUint()];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "integer expected for '%s'", key);
|
|
|
|
arc.mErrors++;
|
|
|
|
node = nullptr;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-09-19 17:14:30 +00:00
|
|
|
template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&pstr, FString **def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
2016-09-19 22:41:22 +00:00
|
|
|
if (!arc.w->inObject() || def == nullptr || pstr != *def)
|
2016-09-18 23:07:51 +00:00
|
|
|
{
|
|
|
|
arc.WriteKey(key);
|
|
|
|
if (pstr == nullptr)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(pstr->GetChars());
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsNull() || val->IsString());
|
2016-09-18 23:07:51 +00:00
|
|
|
if (val->IsNull())
|
|
|
|
{
|
|
|
|
pstr = nullptr;
|
|
|
|
}
|
|
|
|
else if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
pstr = AActor::mStringPropertyData.Alloc(UnicodeToString(val->GetString()));
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string expected for '%s'", key);
|
|
|
|
pstr = nullptr;
|
|
|
|
arc.mErrors++;
|
2016-09-18 23:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-19 22:41:22 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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);
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(pstr.GetChars());
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsNull() || val->IsString());
|
2016-09-19 22:41:22 +00:00
|
|
|
if (val->IsNull())
|
|
|
|
{
|
|
|
|
pstr = "";
|
|
|
|
}
|
|
|
|
else if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
pstr = UnicodeToString(val->GetString());
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string expected for '%s'", key);
|
|
|
|
pstr = "";
|
|
|
|
arc.mErrors++;
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->String(pstr);
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto val = arc.r->FindKey(key);
|
|
|
|
if (val != nullptr)
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
assert(val->IsNull() || val->IsString());
|
2016-09-19 22:41:22 +00:00
|
|
|
if (val->IsNull())
|
|
|
|
{
|
|
|
|
pstr = nullptr;
|
|
|
|
}
|
|
|
|
else if (val->IsString())
|
|
|
|
{
|
2016-11-01 12:33:18 +00:00
|
|
|
pstr = copystring(UnicodeToString(val->GetString()));
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_RED "string expected for '%s'", key);
|
|
|
|
pstr = nullptr;
|
|
|
|
arc.mErrors++;
|
2016-09-19 22:41:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return arc;
|
2016-09-20 11:21:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
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);
|
2016-09-20 16:27:47 +00:00
|
|
|
if (font == nullptr)
|
2016-09-20 11:21:41 +00:00
|
|
|
{
|
- 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.
2016-09-23 12:04:05 +00:00
|
|
|
Printf(TEXTCOLOR_ORANGE "Could not load font %s\n", n);
|
2016-09-20 11:21:41 +00:00
|
|
|
font = SmallFont;
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-20 16:27:47 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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:
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Int64(value.signedval);
|
2016-09-20 16:27:47 +00:00
|
|
|
break;
|
|
|
|
case NumericValue::NM_unsigned:
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Uint64(value.unsignedval);
|
2016-09-20 16:27:47 +00:00
|
|
|
break;
|
|
|
|
case NumericValue::NM_float:
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Double(value.floatval);
|
2016-09-20 16:27:47 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-09-21 19:57:24 +00:00
|
|
|
arc.w->Null();
|
2016-09-20 16:27:47 +00:00
|
|
|
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;
|
|
|
|
}
|