- save the entire engine state as JSON.

The sprite lists may still need optimization. Due to different handling between Blood and the core engine they need to be written out completely which is quite wasteful.
This commit is contained in:
Christoph Oelckers 2020-10-13 19:43:45 +02:00
parent bdc1f66131
commit 817fa8aba3
10 changed files with 180 additions and 181 deletions

View file

@ -82,7 +82,6 @@ kMediumGoo = 2,
// STATNUMS ///////////////////////////////////////////////////// // STATNUMS /////////////////////////////////////////////////////
enum { enum {
kStatNothing = -1,
kStatDecoration = 0, kStatDecoration = 0,
kStatFX = 1, kStatFX = 1,
kStatExplosion = 2, kStatExplosion = 2,

View file

@ -104,7 +104,7 @@ void RemoveSpriteSect(int nSprite)
{ {
headspritesect[nSector] = nextspritesect[nSprite]; headspritesect[nSector] = nextspritesect[nSprite];
} }
sprite[nSprite].sectnum = -1; sprite[nSprite].sectnum = MAXSECTORS;
} }
void InsertSpriteStat(int nSprite, int nStat) void InsertSpriteStat(int nSprite, int nStat)
@ -148,7 +148,7 @@ void RemoveSpriteStat(int nSprite)
{ {
headspritestat[nStat] = nextspritestat[nSprite]; headspritestat[nStat] = nextspritestat[nSprite];
} }
sprite[nSprite].statnum = kStatNothing; sprite[nSprite].statnum = MAXSTATUS;
gStatCount[nStat]--; gStatCount[nStat]--;
} }

View file

@ -201,12 +201,7 @@ typedef struct {
int16_t angoff, pitch, roll; int16_t angoff, pitch, roll;
vec3_t pivot_offset, position_offset; vec3_t pivot_offset, position_offset;
uint8_t flags; uint8_t flags;
uint8_t xpanning, ypanning; // EDuke script hacks.
uint8_t filler;
uint32_t filler2;
float alpha; float alpha;
// NOTE: keep 'tspr' on an 8-byte boundary:
tspriteptr_t tspr;
} spriteext_t; } spriteext_t;
typedef struct { typedef struct {
@ -328,7 +323,6 @@ enum {
PALETTE_TRANSLUC = 1<<2, PALETTE_TRANSLUC = 1<<2,
}; };
EXTERN char showinvisibility;
EXTERN int32_t g_visibility, parallaxvisibility; EXTERN int32_t g_visibility, parallaxvisibility;
// blendtable[1] to blendtable[numalphatabs] are considered to be // blendtable[1] to blendtable[numalphatabs] are considered to be

View file

@ -11,6 +11,7 @@
#include "m_alloc.h" #include "m_alloc.h"
#include "intvec.h" #include "intvec.h"
#include "m_swap.h" #include "m_swap.h"
#include "serializer.h"
////////// Compiler detection ////////// ////////// Compiler detection //////////
@ -242,5 +243,18 @@ void bfirst_search_try(T *const list, uint8_t *const bitmap, T *const eltnumptr,
/* End dependence on compat.o object. */ /* End dependence on compat.o object. */
inline FSerializer& Serialize(FSerializer& arc, const char* key, vec3_t& c, vec3_t* def)
{
if (def && !memcmp(&c, def, sizeof(c))) return arc;
if (arc.BeginObject(key))
{
arc("x", c.x, def? &def->x : nullptr)
("y", c.y, def ? &def->y : nullptr)
("z", c.z, def ? &def->z : nullptr)
.EndObject();
}
return arc;
}
#endif // compat_h_ #endif // compat_h_

View file

@ -943,8 +943,6 @@ int32_t engineInit(void)
xyaspect = -1; xyaspect = -1;
showinvisibility = 0;
voxelmemory.Reset(); voxelmemory.Reset();
for (i=0; i<MAXTILES; i++) for (i=0; i<MAXTILES; i++)

View file

@ -2231,7 +2231,7 @@ void polymost_scansector(int32_t sectnum)
{ {
auto const spr = (uspriteptr_t)&sprite[z]; auto const spr = (uspriteptr_t)&sprite[z];
if ((spr->cstat & 0x8000 && !showinvisibility) || spr->xrepeat == 0 || spr->yrepeat == 0) if ((spr->cstat & 0x8000) || spr->xrepeat == 0 || spr->yrepeat == 0)
continue; continue;
vec2_t const s = { spr->x-globalposx, spr->y-globalposy }; vec2_t const s = { spr->x-globalposx, spr->y-globalposy };
@ -3204,21 +3204,6 @@ void polymost_drawsprite(int32_t snum)
otex.v = -ytex.v * (pxy[3].y + .001f); otex.v = -ytex.v * (pxy[3].y + .001f);
} }
// sprite panning
if (spriteext[spritenum].xpanning)
{
ytex.u -= ytex.d * ((float) (spriteext[spritenum].xpanning) * (1.0f / 255.f)) * ftsiz.x;
otex.u -= otex.d * ((float) (spriteext[spritenum].xpanning) * (1.0f / 255.f)) * ftsiz.x;
drawpoly_srepeat = 1;
}
if (spriteext[spritenum].ypanning)
{
ytex.v -= ytex.d * ((float) (spriteext[spritenum].ypanning) * (1.0f / 255.f)) * ftsiz.y;
otex.v -= otex.d * ((float) (spriteext[spritenum].ypanning) * (1.0f / 255.f)) * ftsiz.y;
drawpoly_trepeat = 1;
}
// Clip sprites to ceilings/floors when no parallaxing and not sloped // Clip sprites to ceilings/floors when no parallaxing and not sloped
if (!(sector[tspr->sectnum].ceilingstat & 3)) if (!(sector[tspr->sectnum].ceilingstat & 3))
{ {
@ -3363,15 +3348,6 @@ void polymost_drawsprite(int32_t snum)
t1 = 1.f - t1; t1 = 1.f - t1;
} }
// sprite panning
if (spriteext[spritenum].xpanning)
{
float const xpan = ((float)(spriteext[spritenum].xpanning) * (1.0f / 255.f));
t0 -= xpan;
t1 -= xpan;
drawpoly_srepeat = 1;
}
xtex.u = (t0 * ryp0 - t1 * ryp1) * gxyaspect * ftsiz.x / (sx0 - sx1); xtex.u = (t0 * ryp0 - t1 * ryp1) * gxyaspect * ftsiz.x / (sx0 - sx1);
ytex.u = 0; ytex.u = 0;
otex.u = t0 * ryp0 * gxyaspect * ftsiz.x - xtex.u * sx0; otex.u = t0 * ryp0 * gxyaspect * ftsiz.x - xtex.u * sx0;
@ -3401,16 +3377,6 @@ void polymost_drawsprite(int32_t snum)
otex.v = -xtex.v * sx0 - ytex.v * sf0; otex.v = -xtex.v * sx0 - ytex.v * sf0;
} }
// sprite panning
if (spriteext[spritenum].ypanning)
{
float const ypan = ((float)(spriteext[spritenum].ypanning) * (1.0f / 255.f)) * ftsiz.y;
xtex.v -= xtex.d * ypan;
ytex.v -= ytex.d * ypan;
otex.v -= otex.d * ypan;
drawpoly_trepeat = 1;
}
// Clip sprites to ceilings/floors when no parallaxing // Clip sprites to ceilings/floors when no parallaxing
if (!(sector[tspr->sectnum].ceilingstat & 1)) if (!(sector[tspr->sectnum].ceilingstat & 1))
{ {
@ -3596,23 +3562,6 @@ void polymost_drawsprite(int32_t snum)
otex.u = ftsiz.x * otex.d - otex.u; otex.u = ftsiz.x * otex.d - otex.u;
} }
// sprite panning
if (spriteext[spritenum].xpanning)
{
float const f = ((float)(spriteext[spritenum].xpanning) * (1.0f / 255.f)) * ftsiz.x;
ytex.u -= ytex.d * f;
otex.u -= otex.d * f;
drawpoly_srepeat = 1;
}
if (spriteext[spritenum].ypanning)
{
float const f = ((float)(spriteext[spritenum].ypanning) * (1.0f / 255.f)) * ftsiz.y;
ytex.v -= ytex.d * f;
otex.v -= otex.d * f;
drawpoly_trepeat = 1;
}
vec2_16_t tempsiz = { (int16_t)tsiz.x, (int16_t)tsiz.y }; vec2_16_t tempsiz = { (int16_t)tsiz.x, (int16_t)tsiz.y };
pow2xsplit = 0; pow2xsplit = 0;

View file

@ -125,6 +125,12 @@ public:
return Serialize(*this, key, obj, save_full? nullptr : &def); return Serialize(*this, key, obj, save_full? nullptr : &def);
} }
template<class T>
FSerializer& operator()(const char* key, T& obj, T* def)
{
return Serialize(*this, key, obj, !def || save_full ? nullptr : def);
}
template<class T> template<class T>
FSerializer &Array(const char *key, T *obj, int count, bool fullcompare = false) FSerializer &Array(const char *key, T *obj, int count, bool fullcompare = false)
{ {
@ -172,6 +178,29 @@ public:
return *this; return *this;
} }
template<class T, class Map>
FSerializer &SparseArray(const char *key, T *obj, int count, const Map &map, bool fullcompare = false)
{
if (BeginArray(key))
{
int max = count;
if (isReading())
{
max = ArraySize();
}
for (int i = 0; i < count; i++)
{
if (map[i])
{
Serialize(*this, nullptr, obj[i], (T*)nullptr);
if (--max < 0) break;
}
}
EndArray();
}
return *this;
}
template<class T> template<class T>
FSerializer &Enum(const char *key, T &obj) FSerializer &Enum(const char *key, T &obj)
{ {

View file

@ -62,11 +62,10 @@ walltype wallbackup[MAXWALLS];
static CompositeSavegameWriter savewriter; static CompositeSavegameWriter savewriter;
static FResourceFile *savereader; static FResourceFile *savereader;
void LoadEngineState();
void SaveEngineState();
void WriteSavePic(FileWriter* file, int width, int height); void WriteSavePic(FileWriter* file, int width, int height);
extern FString BackupSaveGame; extern FString BackupSaveGame;
void SerializeMap(FSerializer &arc); void SerializeMap(FSerializer &arc);
FixedBitArray<MAXSPRITES> activeSprites;
CVAR(String, cl_savedir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(String, cl_savedir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
@ -140,8 +139,7 @@ bool OpenSaveGameForRead(const char *name)
// Load system-side data from savegames. // Load system-side data from savegames.
loadMapBackup(currentLevel->fileName); loadMapBackup(currentLevel->fileName);
LoadEngineState(); SerializeSession(arc);
SerializeSession(arc); // must be AFTER LoadEngineState because it needs info from it.
gi->SerializeGameState(arc); gi->SerializeGameState(arc);
} }
return savereader != nullptr; return savereader != nullptr;
@ -236,7 +234,6 @@ bool OpenSaveGameForWrite(const char* filename, const char *name)
// Handle system-side modules that need to persist data in savegames here, in a central place. // Handle system-side modules that need to persist data in savegames here, in a central place.
savegamesession.OpenWriter(save_formatted); savegamesession.OpenWriter(save_formatted);
SerializeSession(savegamesession); SerializeSession(savegamesession);
SaveEngineState();
gi->SerializeGameState(savegamesession); gi->SerializeGameState(savegamesession);
buff = savegamesession.GetCompressedOutput(); buff = savegamesession.GetCompressedOutput();
AddCompressedSavegameChunk("session.json", buff); AddCompressedSavegameChunk("session.json", buff);
@ -431,24 +428,6 @@ FString G_BuildSaveName (const char *prefix)
#include "build.h" #include "build.h"
#include "mmulti.h" #include "mmulti.h"
static void sv_prespriteextsave()
{
for (int i = 0; i < MAXSPRITES; i++)
if (spriteext[i].mdanimtims)
{
spriteext[i].mdanimtims -= mdtims;
if (spriteext[i].mdanimtims == 0)
spriteext[i].mdanimtims++;
}
}
static void sv_postspriteext()
{
for (int i = 0; i < MAXSPRITES; i++)
if (spriteext[i].mdanimtims)
spriteext[i].mdanimtims += mdtims;
}
static const int magic = 0xbeefcafe; static const int magic = 0xbeefcafe;
void WriteMagic(FileWriter *fw) void WriteMagic(FileWriter *fw)
{ {
@ -468,8 +447,68 @@ void CheckMagic(FileReader& fr)
#define V(x) x #define V(x) x
static spritetype zsp; static spritetype zsp;
static sectortype zsec; static spriteext_t zspx;
static walltype zwal;
FSerializer &Serialize(FSerializer &arc, const char *key, spritetype &c, spritetype *def)
{
def = &zsp; // always delta against 0
if (arc.BeginObject(key))
{
arc("x", c.x, def->x)
("y", c.y, def->y)
("z", c.z, def->z)
("cstat", c.cstat, def->cstat)
("picnum", c.picnum, def->picnum)
("shade", c.shade, def->shade)
("pal", c.pal, def->pal)
("clipdist", c.clipdist, def->clipdist)
("blend", c.blend, def->blend)
("xrepeat", c.xrepeat, def->xrepeat)
("yrepeat", c.yrepeat, def->yrepeat)
("xoffset", c.xoffset, def->xoffset)
("yoffset", c.yoffset, def->yoffset)
("statnum", c.statnum)
("sectnum", c.sectnum)
("ang", c.ang, def->ang)
("owner", c.owner, def->owner)
("xvel", c.xvel, def->xvel)
("yvel", c.yvel, def->yvel)
("zvel", c.zvel, def->zvel)
("lotag", c.lotag, def->lotag)
("hitag", c.hitag, def->hitag)
("extra", c.extra, def->extra)
("detail", c.detail, def->detail)
.EndObject();
}
return arc;
}
FSerializer& Serialize(FSerializer& arc, const char* key, spriteext_t& c, spriteext_t* def)
{
if (arc.isWriting() && c.mdanimtims)
{
c.mdanimtims -= mdtims;
if (c.mdanimtims == 0) c.mdanimtims++;
}
def = &zspx; // always delta against 0
if (arc.BeginObject(key))
{
arc("mdanimtims", c.mdanimtims, def->mdanimtims)
("mdanimcur", c.mdanimcur, def->mdanimcur)
("angoff", c.angoff, def->angoff)
("pitch", c.pitch, def->pitch)
("roll", c.roll, def->roll)
("pivot_offset", c.pivot_offset, def->pivot_offset)
("position_offset", c.position_offset, def->position_offset)
("flags", c.flags, def->flags)
("alpha", c.alpha, def->alpha)
.EndObject();
}
if (c.mdanimtims) c.mdanimtims += mdtims;
return arc;
}
FSerializer &Serialize(FSerializer &arc, const char *key, sectortype &c, sectortype *def) FSerializer &Serialize(FSerializer &arc, const char *key, sectortype &c, sectortype *def)
{ {
@ -529,109 +568,88 @@ FSerializer &Serialize(FSerializer &arc, const char *key, walltype &c, walltype
return arc; return arc;
} }
void SerializeMap(FSerializer& arc) void SerializeMap(FSerializer& arc)
{ {
// create a map of all used sprites so that we can use that elsewhere to only save what's needed.
activeSprites.Zero();
if (arc.isWriting())
{
for (int i=0; i<MAXSPRITES;i++)
{
if (sprite[i].statnum != MAXSTATUS)
{
activeSprites.Set(i);
}
}
// simplify the data a bit for better compression.
for (int i = 0; i < MAXSPRITES; i++)
{
if (nextspritestat[i] == i + 1) nextspritestat[i] = -2;
if (nextspritesect[i] == i + 1) nextspritesect[i] = -2;
if (prevspritestat[i] == i - 1) prevspritestat[i] = -2;
if (prevspritesect[i] == i - 1) prevspritesect[i] = -2;
}
}
else
{
memset(sprite, 0, sizeof(sprite[0]) * MAXSPRITES);
initspritelists();
zsp = sprite[0];
}
if (arc.BeginObject("engine")) if (arc.BeginObject("engine"))
{ {
arc ("numsectors", numsectors) arc.SerializeMemory("activesprites", activeSprites.Storage(), activeSprites.StorageSize())
.SparseArray("sprites", sprite, MAXSPRITES, activeSprites)
.SparseArray("spriteext", spriteext, MAXSPRITES, activeSprites)
("numsectors", numsectors)
.Array("sectors", sector, sectorbackup, numsectors) .Array("sectors", sector, sectorbackup, numsectors)
("numwalls", numwalls) ("numwalls", numwalls)
.Array("walls", wall, wallbackup, numwalls) .Array("walls", wall, wallbackup, numwalls)
.EndObject(); .Array("headspritestat", headspritestat, MAXSTATUS + 1)
} .Array("nextspritestat", nextspritestat, MAXSPRITES)
.Array("prevspritestat", prevspritestat, MAXSPRITES)
.Array("headspritesect", headspritesect, MAXSECTORS + 1)
.Array("nextspritesect", nextspritesect, MAXSPRITES)
.Array("prevspritesect", prevspritesect, MAXSPRITES)
("tailspritefree", tailspritefree)
("myconnectindex", myconnectindex)
("connecthead", connecthead)
.Array("connectpoint2", connectpoint2, countof(connectpoint2))
("randomseed", randomseed)
("numshades", numshades) // is this really needed?
("visibility", g_visibility)
("parallaxtype", parallaxtype)
("parallaxvisibility", parallaxvisibility)
("parallaxyo", parallaxyoffs_override)
("parallaxys", parallaxyscale_override)
("pskybits", pskybits_override)
("numsprites", Numsprites);
} if (arc.BeginArray("picanm")) // write this in the most compact form available.
void SaveEngineState()
{
auto fw = WriteSavegameChunk("engine.bin");
fw->Write(sprite, sizeof(spritetype) * MAXSPRITES);
WriteMagic(fw);
fw->Write(headspritesect, sizeof(headspritesect));
fw->Write(prevspritesect, sizeof(prevspritesect));
fw->Write(nextspritesect, sizeof(nextspritesect));
fw->Write(headspritestat, sizeof(headspritestat));
fw->Write(prevspritestat, sizeof(prevspritestat));
fw->Write(nextspritestat, sizeof(nextspritestat));
WriteMagic(fw);
for (int i = 0; i < MAXTILES; i++)
{
fw->Write(&picanm[i], sizeof(picanm[i]));
}
WriteMagic(fw);
fw->Write(&tailspritefree, sizeof(tailspritefree));
fw->Write(&myconnectindex, sizeof(myconnectindex));
fw->Write(&connecthead, sizeof(connecthead));
fw->Write(connectpoint2, sizeof(connectpoint2));
fw->Write(&randomseed, sizeof(randomseed));
fw->Write(&numshades, sizeof(numshades));
fw->Write(&showinvisibility, sizeof(showinvisibility));
WriteMagic(fw);
fw->Write(&g_visibility, sizeof(g_visibility));
fw->Write(&parallaxtype, sizeof(parallaxtype));
fw->Write(&parallaxvisibility, sizeof(parallaxvisibility));
fw->Write(&parallaxyoffs_override, sizeof(parallaxyoffs_override));
fw->Write(&parallaxyscale_override, sizeof(parallaxyscale_override));
fw->Write(&pskybits_override, sizeof(pskybits_override));
WriteMagic(fw);
fw->Write(&Numsprites, sizeof(Numsprites));
sv_prespriteextsave();
fw->Write(spriteext, sizeof(spriteext_t) * MAXSPRITES);
fw->Write(&randomseed, sizeof(randomseed));
sv_postspriteext();
WriteMagic(fw);
}
void LoadEngineState()
{
auto fr = ReadSavegameChunk("engine.bin");
if (fr.isOpen())
{
memset(sprite, 0, sizeof(sprite[0]) * MAXSPRITES);
fr.Read(sprite, sizeof(spritetype) * MAXSPRITES);
CheckMagic(fr);
fr.Read(headspritesect, sizeof(headspritesect));
fr.Read(prevspritesect, sizeof(prevspritesect));
fr.Read(nextspritesect, sizeof(nextspritesect));
fr.Read(headspritestat, sizeof(headspritestat));
fr.Read(prevspritestat, sizeof(prevspritestat));
fr.Read(nextspritestat, sizeof(nextspritestat));
CheckMagic(fr);
for (int i = 0; i < MAXTILES; i++)
{ {
fr.Read(&picanm[i], sizeof(picanm[i])); for (int i = 0; i < MAXTILES; i++)
{
arc(nullptr, picanm[i].sf)
(nullptr, picanm[i].extra);
}
arc.EndArray();
} }
CheckMagic(fr);
fr.Read(&tailspritefree, sizeof(tailspritefree)); arc.EndObject();
fr.Read(&myconnectindex, sizeof(myconnectindex)); }
fr.Read(&connecthead, sizeof(connecthead));
fr.Read(connectpoint2, sizeof(connectpoint2));
fr.Read(&randomseed, sizeof(randomseed));
fr.Read(&numshades, sizeof(numshades));
fr.Read(&showinvisibility, sizeof(showinvisibility));
CheckMagic(fr);
fr.Read(&g_visibility, sizeof(g_visibility));
fr.Read(&parallaxtype, sizeof(parallaxtype));
fr.Read(&parallaxvisibility, sizeof(parallaxvisibility));
fr.Read(&parallaxyoffs_override, sizeof(parallaxyoffs_override));
fr.Read(&parallaxyscale_override, sizeof(parallaxyscale_override));
fr.Read(&pskybits_override, sizeof(pskybits_override));
CheckMagic(fr);
fr.Read(&Numsprites, sizeof(Numsprites)); // Undo the simplification.
fr.Read(spriteext, sizeof(spriteext_t) * MAXSPRITES); for (int i = 0; i < MAXSPRITES; i++)
fr.Read(&randomseed, sizeof(randomseed)); {
sv_postspriteext(); if (nextspritestat[i] == -2) nextspritestat[i] = i + 1;
CheckMagic(fr); if (nextspritesect[i] == -2) nextspritesect[i] = i + 1;
if (prevspritestat[i] == -2) prevspritestat[i] = i - 1;
fr.Close(); if (prevspritesect[i] == -2) prevspritesect[i] = i - 1;
} }
} }

View file

@ -20,8 +20,6 @@ int G_ValidateSavegame(FileReader &fr, FString *savetitle, bool formenu);
void G_LoadGame(const char* filename); void G_LoadGame(const char* filename);
void G_SaveGame(const char* fn, const char* desc, bool ok4q, bool forceq); void G_SaveGame(const char* fn, const char* desc, bool ok4q, bool forceq);
void SaveEngineState();
void LoadEngineState();
void M_Autosave(); void M_Autosave();
#define SAVEGAME_EXT ".dsave" #define SAVEGAME_EXT ".dsave"

View file

@ -4632,7 +4632,7 @@ NewStateGroup(short SpriteNum, STATEp StateGroup[])
// Kind of a goofy check, but it should catch alot of invalid states! // Kind of a goofy check, but it should catch alot of invalid states!
// BTW, 6144 is the max tile number allowed in editart. // BTW, 6144 is the max tile number allowed in editart.
if (u->State && (u->State->Pic < 0 || u->State->Pic > 6144)) // JBF: verify this! if (u->State && (u->State->Pic < 0 || u->State->Pic > MAXTILES)) // JBF: verify this!
return 0; return 0;
u->Rot = StateGroup; u->Rot = StateGroup;