Optimise undo history memory usage
By storing only the differences between Snapshots where possible. NOTE: This may be reverted later if it causes too much trouble.
This commit is contained in:
parent
ea07244119
commit
eac92d1b04
@ -185,6 +185,8 @@ void GameController::HistoryRestore()
|
||||
|
||||
void GameController::HistorySnapshot()
|
||||
{
|
||||
// * Calling HistorySnapshot means the user decided to use the current state and
|
||||
// forfeit the option to go back to whatever they Ctrl+Z'd their way back from.
|
||||
beforeRestore.reset();
|
||||
gameModel->HistoryPush(gameModel->GetSimulation()->CreateSnapshot());
|
||||
}
|
||||
@ -196,6 +198,8 @@ void GameController::HistoryForward()
|
||||
return;
|
||||
}
|
||||
gameModel->HistoryForward();
|
||||
// * If gameModel has nothing more to give, we've Ctrl+Y'd our way back to the original
|
||||
// state; restore this instead, then get rid of it.
|
||||
auto ¤t = gameModel->HistoryCurrent() ? *gameModel->HistoryCurrent() : *beforeRestore;
|
||||
gameModel->GetSimulation()->Restore(current);
|
||||
Client::Ref().OverwriteAuthorInfo(current.Authors);
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "simulation/Gravity.h"
|
||||
#include "simulation/Simulation.h"
|
||||
#include "simulation/Snapshot.h"
|
||||
#include "simulation/SnapshotDelta.h"
|
||||
#include "simulation/ElementClasses.h"
|
||||
#include "simulation/ElementGraphics.h"
|
||||
#include "simulation/ToolClasses.h"
|
||||
@ -34,6 +35,12 @@
|
||||
#include "gui/game/DecorationTool.h"
|
||||
#include "gui/interface/Engine.h"
|
||||
|
||||
HistoryEntry::~HistoryEntry()
|
||||
{
|
||||
// * Needed because Snapshot and SnapshotDelta are incomplete types in GameModel.h,
|
||||
// so the default dtor for ~HistoryEntry cannot be generated.
|
||||
}
|
||||
|
||||
GameModel::GameModel():
|
||||
clipboard(NULL),
|
||||
placeSave(NULL),
|
||||
@ -550,12 +557,146 @@ int GameModel::GetDecoSpace()
|
||||
return this->decoSpace;
|
||||
}
|
||||
|
||||
// * SnapshotDelta d is the difference between the two Snapshots A and B (i.e. d = B - A)
|
||||
// if auto d = SnapshotDelta::FromSnapshots(A, B). In this case, a Snapshot that is
|
||||
// identical to B can be constructed from d and A via d.Forward(A) (i.e. B = A + d)
|
||||
// and a Snapshot that is identical to A can be constructed from d and B via
|
||||
// d.Restore(B) (i.e. A = B - d). SnapshotDeltas often consume less memory than Snapshots,
|
||||
// although pathological cases of pairs of Snapshots exist, the SnapshotDelta constructed
|
||||
// from which actually consumes more than the two snapshots combined.
|
||||
// * GameModel::history is an N-item deque of HistoryEntry structs, each of which owns either
|
||||
// a SnapshotDelta, except for history[N-1], which always owns a Snapshot. A logical Snapshot
|
||||
// accompanies each item in GameModel::history. This logical Snapshot may or may not be
|
||||
// materialised (present in memory). If an item owns an actual Snapshot, the aforementioned
|
||||
// logical Snapshot is this materialised Snapshot. If, however, an item owns a SnapshotDelta d,
|
||||
// the accompanying logical Snapshot A is the Snapshot obtained via A = d.Restore(B), where B
|
||||
// is the logical Snapshot accompanying the next (at an index that is one higher than the
|
||||
// index of this item) item in history. Slightly more visually:
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// N - 1 | Snapshot A | Snapshot A | A |
|
||||
// | | | / |
|
||||
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B |
|
||||
// | | | / |
|
||||
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
||||
// | | | / |
|
||||
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
||||
// | | | / |
|
||||
// ... | ... | ... | ... ... |
|
||||
//
|
||||
// * GameModel::historyPosition is an integer in the closed range 0 to N, which is decremented
|
||||
// by GameModel::HistoryRestore and incremented by GameModel::HistoryForward, by 1 at a time.
|
||||
// GameModel::historyCurrent "follows" historyPosition such that it always holds a Snapshot
|
||||
// that is identical to the logical Snapshot of history[historyPosition], except when
|
||||
// historyPosition = N, in which case it's empty. This following behaviour is achieved either
|
||||
// by "stepping" historyCurrent by Forwarding and Restoring it via the SnapshotDelta in
|
||||
// history[historyPosition], cloning the Snapshot in history[historyPosition] into it if
|
||||
// historyPosition = N-1, or clearing if it historyPosition = N.
|
||||
// * GameModel::historyCurrent is lost when a new Snapshot item is pushed into GameModel::history.
|
||||
// This item appears wherever historyPosition currently points, and every other item above it
|
||||
// is deleted. If historyPosition is below N, this gets rid of the Snapshot in history[N-1].
|
||||
// Thus, N is set to historyPosition, after which the new Snapshot is pushed and historyPosition
|
||||
// is incremented to the new N.
|
||||
// * Pushing a new Snapshot into the history is a bit involved:
|
||||
// * If there are no history entries yet, the new Snapshot is simply placed into GameModel::history.
|
||||
// From now on, we consider cases in which GameModel::history is originally not empty.
|
||||
//
|
||||
// === after pushing Snapshot A' into the history
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// 0 | Snapshot A | Snapshot A | A |
|
||||
//
|
||||
// * If there were discarded history entries (i.e. the user decided to continue from some state
|
||||
// which they arrived to via at least one Ctrl+Z), history[N-2] is a SnapshotDelta that when
|
||||
// Forwarded with the logical Snapshot of history[N-2] yields the logical Snapshot of history[N-1]
|
||||
// from before the new item was pushed. This is not what we want, so we replace it with a
|
||||
// SnapshotDelta that is the difference between the logical Snapshot of history[N-2] and the
|
||||
// Snapshot freshly placed in history[N-1].
|
||||
//
|
||||
// === after pushing Snapshot A' into the history
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// N - 1 | Snapshot A' | Snapshot A' | A' | b needs to be replaced with b',
|
||||
// | | | / | B+b'=A'; otherwise we'd run
|
||||
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B | into problems when trying to
|
||||
// | | | / | reconstruct B from A' and b
|
||||
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C | in HistoryRestore.
|
||||
// | | | / |
|
||||
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
||||
// | | | / |
|
||||
// ... | ... | ... | ... ... |
|
||||
//
|
||||
// === after replacing b with b'
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// N - 1 | Snapshot A' | Snapshot A' | A' |
|
||||
// | | | / |
|
||||
// N - 2 | SnapshotDelta b'| Snapshot B | B+b'=A' b'-B |
|
||||
// | | | / |
|
||||
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
||||
// | | | / |
|
||||
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
||||
// | | | / |
|
||||
// ... | ... | ... | ... ... |
|
||||
//
|
||||
// * If there weren't any discarded history entries, history[N-2] is now also a Snapshot. Since
|
||||
// the freshly pushed Snapshot in history[N-1] should be the only Snapshot in history, this is
|
||||
// replaced with the SnapshotDelta that is the difference between history[N-2] and the Snapshot
|
||||
// freshly placed in history[N-1].
|
||||
//
|
||||
// === after pushing Snapshot A' into the history
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// N - 1 | Snapshot A' | Snapshot A' | A' | A needs to be converted to a,
|
||||
// | | | | otherwise Snapshots would litter
|
||||
// N - 1 | Snapshot A | Snapshot A | A | GameModel::history, which we
|
||||
// | | | / | want to avoid because they
|
||||
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B | waste a ton of memory
|
||||
// | | | / |
|
||||
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
||||
// | | | / |
|
||||
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
||||
// | | | / |
|
||||
// ... | ... | ... | ... ... |
|
||||
//
|
||||
// === after replacing A with a
|
||||
//
|
||||
// i | history[i] | the logical Snapshot | relationships |
|
||||
// | | accompanying history[i] | |
|
||||
// -------|-----------------|-------------------------|---------------|
|
||||
// | | | |
|
||||
// N - 1 | Snapshot A' | Snapshot A' | A' |
|
||||
// | | | / |
|
||||
// N - 1 | SnapshotDelta a | Snapshot A | A+a=A' a-A |
|
||||
// | | | / |
|
||||
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B |
|
||||
// | | | / |
|
||||
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
||||
// | | | / |
|
||||
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
||||
// | | | / |
|
||||
// ... | ... | ... | ... ... |
|
||||
//
|
||||
// * After all this, the front of the deque is truncated such that there are on more than
|
||||
// undoHistoryLimit entries left.
|
||||
|
||||
const Snapshot *GameModel::HistoryCurrent() const
|
||||
{
|
||||
if (historyPosition > history.size())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return historyCurrent.get();
|
||||
}
|
||||
|
||||
@ -566,8 +707,19 @@ bool GameModel::HistoryCanRestore() const
|
||||
|
||||
void GameModel::HistoryRestore()
|
||||
{
|
||||
if (!HistoryCanRestore())
|
||||
{
|
||||
return;
|
||||
}
|
||||
historyPosition -= 1U;
|
||||
historyCurrent = std::make_unique<Snapshot>(*history[historyPosition]);
|
||||
if (history[historyPosition].snap)
|
||||
{
|
||||
historyCurrent = std::make_unique<Snapshot>(*history[historyPosition].snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
historyCurrent = history[historyPosition].delta->Restore(*historyCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameModel::HistoryCanForward() const
|
||||
@ -577,17 +729,49 @@ bool GameModel::HistoryCanForward() const
|
||||
|
||||
void GameModel::HistoryForward()
|
||||
{
|
||||
if (!HistoryCanForward())
|
||||
{
|
||||
return;
|
||||
}
|
||||
historyPosition += 1U;
|
||||
historyCurrent = historyPosition < history.size() ? std::make_unique<Snapshot>(*history[historyPosition]) : nullptr;
|
||||
if (historyPosition == history.size())
|
||||
{
|
||||
historyCurrent = nullptr;
|
||||
}
|
||||
else if (history[historyPosition].snap)
|
||||
{
|
||||
historyCurrent = std::make_unique<Snapshot>(*history[historyPosition].snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
historyCurrent = history[historyPosition - 1U].delta->Forward(*historyCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
void GameModel::HistoryPush(std::unique_ptr<Snapshot> last)
|
||||
{
|
||||
Snapshot *rebaseOnto = nullptr;
|
||||
if (historyPosition)
|
||||
{
|
||||
rebaseOnto = history.back().snap.get();
|
||||
if (historyPosition < history.size())
|
||||
{
|
||||
historyCurrent = history[historyPosition - 1U].delta->Restore(*historyCurrent);
|
||||
rebaseOnto = historyCurrent.get();
|
||||
}
|
||||
}
|
||||
while (historyPosition < history.size())
|
||||
{
|
||||
history.pop_back();
|
||||
}
|
||||
history.push_back(std::move(last));
|
||||
if (rebaseOnto)
|
||||
{
|
||||
auto &prev = history.back();
|
||||
prev.delta = SnapshotDelta::FromSnapshots(*rebaseOnto, *last);
|
||||
prev.snap.reset();
|
||||
}
|
||||
history.emplace_back();
|
||||
history.back().snap = std::move(last);
|
||||
historyPosition += 1U;
|
||||
historyCurrent.reset();
|
||||
while (undoHistoryLimit < history.size())
|
||||
|
@ -22,6 +22,7 @@ class SaveFile;
|
||||
class Simulation;
|
||||
class Renderer;
|
||||
class Snapshot;
|
||||
struct SnapshotDelta;
|
||||
class GameSave;
|
||||
|
||||
class ToolSelection
|
||||
@ -33,6 +34,14 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
struct HistoryEntry
|
||||
{
|
||||
std::unique_ptr<Snapshot> snap;
|
||||
std::unique_ptr<SnapshotDelta> delta;
|
||||
|
||||
~HistoryEntry();
|
||||
};
|
||||
|
||||
class GameModel
|
||||
{
|
||||
private:
|
||||
@ -65,7 +74,7 @@ private:
|
||||
Tool * regularToolset[4];
|
||||
User currentUser;
|
||||
float toolStrength;
|
||||
std::deque<std::unique_ptr<Snapshot>> history;
|
||||
std::deque<HistoryEntry> history;
|
||||
std::unique_ptr<Snapshot> historyCurrent;
|
||||
unsigned int historyPosition;
|
||||
unsigned int undoHistoryLimit;
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Particle.h"
|
||||
#include "Sign.h"
|
||||
#include "Stickman.h"
|
||||
#include "json/json.h"
|
||||
|
||||
class Snapshot
|
||||
|
297
src/simulation/SnapshotDelta.cpp
Normal file
297
src/simulation/SnapshotDelta.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
#include "SnapshotDelta.h"
|
||||
|
||||
#include "common/tpt-compat.h"
|
||||
#include "common/tpt-minmax.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
// * A SnapshotDelta is a bidirectional difference type between Snapshots, defined such
|
||||
// that SnapshotDelta d = SnapshotDelta::FromSnapshots(A, B) yields a SnapshotDelta which can be
|
||||
// used to construct a Snapshot identical to A via d.Restore(B) and a Snapshot identical
|
||||
// to B via d.Forward(A). Thus, d = B - A, A = B - d and B = A + d.
|
||||
// * Fields in Snapshot can be classified into two groups:
|
||||
// * Fields of static size, whose sizes are identical to the size of the corresponding field
|
||||
// in all other Snapshots. Example of these fields include AmbientHeat (whose size depends
|
||||
// on XRES, YRES and CELL, all compile-time constants) and WirelessData (whose size depends
|
||||
// on CHANNELS, another compile-time constant). Note that these fields would be of "static
|
||||
// size" even if their sizes weren't derived from compile-time constants, as they'd still
|
||||
// be the same size throughout the life of a Simulation, and thus any Snapshot created from it.
|
||||
// * Fields of dynamic size, whose sizes may be different between Snapshots. These are, fortunately,
|
||||
// the minority: Particles, signs and Authors.
|
||||
// * Each field in Snapshot has a mirror set of fields in SnapshotDelta. Fields of static size
|
||||
// have mirror fields whose type is HunkVector, templated by the item type of the
|
||||
// corresponding field; these fields are handled in a uniform manner. Fields of dynamic size are
|
||||
// handled in a non-uniform, case-by-case manner.
|
||||
// * A HunkVector is generated from two streams of identical size and is a collection
|
||||
// of Hunks, a Hunk is an offset combined with a collection of Diffs, and a Diff is a pair of values,
|
||||
// one originating from one stream and the other from the other. Thus, Hunks represent contiguous
|
||||
// sequences of differences between the two streams, and a HunkVector is a compact way to represent
|
||||
// all differences between the two streams it's generated from. In this case, these streams are
|
||||
// the data in corresponding fields of static size in two Snapshots, and the HunkVector is the
|
||||
// respective field in the SnapshotDelta that is the difference between the two Snapshots.
|
||||
// * FillHunkVectorPtr is the d = B - A operation, which takes two Snapshot fields of static size and
|
||||
// the corresponding SnapshotDelta field, and fills the latter with the HunkVector generated
|
||||
// from the former streams.
|
||||
// * ApplyHunkVector<true> is the A = B - d operation, which takes a field of a SnapshotDelta and
|
||||
// the corresponding field of a "newer" Snapshot, and fills the latter with the "old" values.
|
||||
// * ApplyHunkVector<false> is the B = A + d operation, which takes a field of a SnapshotDelta and
|
||||
// the corresponding field of an "older" Snapshot, and fills the latter with the "new" values.
|
||||
// * This difference type is intended for fields of static size. This covers all fields in Snapshot
|
||||
// except for Particles, signs, and Authors.
|
||||
// * A SingleDiff is, unsurprisingly enough, a single Diff, with an accompanying bool that signifies
|
||||
// whether the Diff does in fact hold the "old" value of a field in the "old" Snapshot and the "new"
|
||||
// value of the same field in the "new" Snapshot. If this bool is false, the data in the fields
|
||||
// of both Snapshots are equivalent and the SingleDiff should be ignored. If it's true, the
|
||||
// SingleDiff represents the difference between these fields.
|
||||
// * FillSingleDiff is the d = B - A operation, while ApplySingleDiff<false> and ApplySingleDiff<true>
|
||||
// are the A = B - d and B = A + d operations. These are self-explanatory.
|
||||
// * This difference type is intended for fields of dynamic size whose data doesn't change often and
|
||||
// doesn't consume too much memory. This covers the Snapshot fields signs and Authors.
|
||||
// * This leaves Snapshot::Particles. This field mirrors Simulation::parts, which is actually also
|
||||
// a field of static size, but since most of the time most of this array is empty, it doesn't make
|
||||
// sense to store all of it in a Snapshot (unlike Air::hv, which can be fairly chaotic (i.e. may have
|
||||
// a lot of interesting data in all of its cells) when ambient heat simulation is enabled, or
|
||||
// Simulation::wireless, which is not big enough to need compression). This makes Snapshots smaller,
|
||||
// but the life of a SnapshotDelta developer harder. The following, relatively simple approach is
|
||||
// taken, as a sort of compromise between simplicity and memory usage:
|
||||
// * The common part of the Particles arrays in the old and the new Snapshots is identified: this is
|
||||
// the overlapping part, i.e. the first size cells of both arrays, where
|
||||
// size = min(old.Particles.size(), new.Particles.size()), and a HunkVector is generated from it,
|
||||
// as though it was a field of static size. For our purposes, it is indeed Static Enough:tm:, for
|
||||
// it only needs to be the same size as the common part of the Particles arrays of the two Snapshots.
|
||||
// * The rest of both Particles arrays is copied to the extra fields extraPartsOld and extraPartsNew.
|
||||
// * One more trick is at work here: Particle structs are actually compared property-by-property rather
|
||||
// than as a whole. This ends up being beneficial to memory usage, as many properties (e.g. type
|
||||
// and ctype) don't often change over time, while others (e.g. x and y) do. Currently, all Particle
|
||||
// properties are 4-byte integral values, which makes it feasible to just reinterpret_cast Particle
|
||||
// structs as arrays of uint32_t values and generate HunkVectors from the resulting streams instead.
|
||||
// This assumption is enforced by the following static_asserts. The same trick is used for playerst
|
||||
// structs, even though Snapshot::stickmen is not big enough for us to benefit from this. The
|
||||
// alternative would have been to implement operator ==(const playerst &, const playerst &), which
|
||||
// would have been tedious.
|
||||
|
||||
constexpr size_t ParticleUint32Count = sizeof(Particle) / sizeof(uint32_t);
|
||||
static_assert(sizeof(Particle) % sizeof(uint32_t) == 0, "fix me");
|
||||
|
||||
constexpr size_t playerstUint32Count = sizeof(playerst) / sizeof(uint32_t);
|
||||
static_assert(sizeof(playerst) % sizeof(uint32_t) == 0, "fix me");
|
||||
|
||||
// * Needed by FillHunkVector for handling Snapshot::stickmen.
|
||||
bool operator ==(const playerst &lhs, const playerst &rhs)
|
||||
{
|
||||
auto match = true;
|
||||
for (auto i = 0U; i < 16U; ++i)
|
||||
{
|
||||
match = match && lhs.legs[i] == rhs.legs[i];
|
||||
}
|
||||
for (auto i = 0U; i < 8U; ++i)
|
||||
{
|
||||
match = match && lhs.accs[i] == rhs.accs[i];
|
||||
}
|
||||
return match &&
|
||||
lhs.comm == rhs.comm &&
|
||||
lhs.pcomm == rhs.pcomm &&
|
||||
lhs.elem == rhs.elem &&
|
||||
lhs.spwn == rhs.spwn &&
|
||||
lhs.frames == rhs.frames &&
|
||||
lhs.rocketBoots == rhs.rocketBoots &&
|
||||
lhs.fan == rhs.fan &&
|
||||
lhs.spawnID == rhs.spawnID;
|
||||
}
|
||||
|
||||
// * Needed by FillSingleDiff for handling Snapshot::signs.
|
||||
bool operator ==(const std::vector<sign> &lhs, const std::vector<sign> &rhs)
|
||||
{
|
||||
if (lhs.size() != rhs.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto i = 0U; i < lhs.size(); ++i)
|
||||
{
|
||||
if (!(lhs[i].x == rhs[i].x &&
|
||||
lhs[i].y == rhs[i].y &&
|
||||
lhs[i].ju == rhs[i].ju &&
|
||||
lhs[i].text == rhs[i].text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Item>
|
||||
void FillHunkVectorPtr(const Item *oldItems, const Item *newItems, SnapshotDelta::HunkVector<Item> &out, size_t size)
|
||||
{
|
||||
auto i = 0U;
|
||||
bool different = false;
|
||||
auto offset = 0U;
|
||||
auto markDifferent = [oldItems, newItems, &out, &i, &different, &offset](bool mark) {
|
||||
if (mark && !different)
|
||||
{
|
||||
different = true;
|
||||
offset = i;
|
||||
}
|
||||
else if (!mark && different)
|
||||
{
|
||||
different = false;
|
||||
auto size = i - offset;
|
||||
out.emplace_back();
|
||||
auto &hunk = out.back();
|
||||
hunk.offset = offset;
|
||||
auto &diffs = hunk.diffs;
|
||||
diffs.resize(size);
|
||||
for (auto j = 0U; j < size; ++j)
|
||||
{
|
||||
diffs[j].oldItem = oldItems[offset + j];
|
||||
diffs[j].newItem = newItems[offset + j];
|
||||
}
|
||||
}
|
||||
};
|
||||
while (i < size)
|
||||
{
|
||||
markDifferent(!(oldItems[i] == newItems[i]));
|
||||
i += 1U;
|
||||
}
|
||||
markDifferent(false);
|
||||
}
|
||||
|
||||
template<class Item>
|
||||
void FillHunkVector(const std::vector<Item> &oldItems, const std::vector<Item> &newItems, SnapshotDelta::HunkVector<Item> &out)
|
||||
{
|
||||
FillHunkVectorPtr<Item>(&oldItems[0], &newItems[0], out, std::min(oldItems.size(), newItems.size()));
|
||||
}
|
||||
|
||||
template<class Item>
|
||||
void FillSingleDiff(const Item &oldItem, const Item &newItem, SnapshotDelta::SingleDiff<Item> &out)
|
||||
{
|
||||
if (oldItem != newItem)
|
||||
{
|
||||
out.valid = true;
|
||||
out.diff.oldItem = oldItem;
|
||||
out.diff.newItem = newItem;
|
||||
}
|
||||
}
|
||||
|
||||
template<bool UseOld, class Item>
|
||||
void ApplyHunkVectorPtr(const SnapshotDelta::HunkVector<Item> &in, Item *items)
|
||||
{
|
||||
for (auto &hunk : in)
|
||||
{
|
||||
auto offset = hunk.offset;
|
||||
auto &diffs = hunk.diffs;
|
||||
for (auto j = 0U; j < diffs.size(); ++j)
|
||||
{
|
||||
items[offset + j] = UseOld ? diffs[j].oldItem : diffs[j].newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<bool UseOld, class Item>
|
||||
void ApplyHunkVector(const SnapshotDelta::HunkVector<Item> &in, std::vector<Item> &items)
|
||||
{
|
||||
ApplyHunkVectorPtr<UseOld, Item>(in, &items[0]);
|
||||
}
|
||||
|
||||
template<bool UseOld, class Item>
|
||||
void ApplySingleDiff(const SnapshotDelta::SingleDiff<Item> &in, Item &item)
|
||||
{
|
||||
if (in.valid)
|
||||
{
|
||||
item = UseOld ? in.diff.oldItem : in.diff.newItem;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SnapshotDelta> SnapshotDelta::FromSnapshots(const Snapshot &oldSnap, const Snapshot &newSnap)
|
||||
{
|
||||
auto ptr = std::make_unique<SnapshotDelta>();
|
||||
auto &delta = *ptr;
|
||||
FillHunkVector(oldSnap.AirPressure , newSnap.AirPressure , delta.AirPressure );
|
||||
FillHunkVector(oldSnap.AirVelocityX , newSnap.AirVelocityX , delta.AirVelocityX );
|
||||
FillHunkVector(oldSnap.AirVelocityY , newSnap.AirVelocityY , delta.AirVelocityY );
|
||||
FillHunkVector(oldSnap.AmbientHeat , newSnap.AmbientHeat , delta.AmbientHeat );
|
||||
FillHunkVector(oldSnap.GravVelocityX , newSnap.GravVelocityX , delta.GravVelocityX );
|
||||
FillHunkVector(oldSnap.GravVelocityY , newSnap.GravVelocityY , delta.GravVelocityY );
|
||||
FillHunkVector(oldSnap.GravValue , newSnap.GravValue , delta.GravValue );
|
||||
FillHunkVector(oldSnap.GravMap , newSnap.GravMap , delta.GravMap );
|
||||
FillHunkVector(oldSnap.BlockMap , newSnap.BlockMap , delta.BlockMap );
|
||||
FillHunkVector(oldSnap.ElecMap , newSnap.ElecMap , delta.ElecMap );
|
||||
FillHunkVector(oldSnap.FanVelocityX , newSnap.FanVelocityX , delta.FanVelocityX );
|
||||
FillHunkVector(oldSnap.FanVelocityY , newSnap.FanVelocityY , delta.FanVelocityY );
|
||||
FillHunkVector(oldSnap.WirelessData , newSnap.WirelessData , delta.WirelessData );
|
||||
FillSingleDiff(oldSnap.signs , newSnap.signs , delta.signs );
|
||||
FillSingleDiff(oldSnap.Authors , newSnap.Authors , delta.Authors );
|
||||
FillHunkVectorPtr(reinterpret_cast<const uint32_t *>(&oldSnap.PortalParticles[0]), reinterpret_cast<const uint32_t *>(&newSnap.PortalParticles[0]), delta.PortalParticles, newSnap.PortalParticles.size() * ParticleUint32Count);
|
||||
FillHunkVectorPtr(reinterpret_cast<const uint32_t *>(&oldSnap.stickmen[0]) , reinterpret_cast<const uint32_t *>(&newSnap.stickmen[0] ), delta.stickmen , newSnap.stickmen .size() * playerstUint32Count);
|
||||
|
||||
// * Slightly more interesting; this will only diff the common parts, the rest is copied separately.
|
||||
auto commonSize = std::min(oldSnap.Particles.size(), newSnap.Particles.size());
|
||||
FillHunkVectorPtr(reinterpret_cast<const uint32_t *>(&oldSnap.Particles[0]), reinterpret_cast<const uint32_t *>(&newSnap.Particles[0]), delta.commonParticles, commonSize * ParticleUint32Count);
|
||||
delta.extraPartsOld.resize(oldSnap.Particles.size() - commonSize);
|
||||
std::copy(oldSnap.Particles.begin() + commonSize, oldSnap.Particles.end(), delta.extraPartsOld.begin());
|
||||
delta.extraPartsNew.resize(newSnap.Particles.size() - commonSize);
|
||||
std::copy(newSnap.Particles.begin() + commonSize, newSnap.Particles.end(), delta.extraPartsNew.begin());
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Snapshot> SnapshotDelta::Forward(const Snapshot &oldSnap)
|
||||
{
|
||||
auto ptr = std::make_unique<Snapshot>(oldSnap);
|
||||
auto &newSnap = *ptr;
|
||||
ApplyHunkVector<false>(AirPressure , newSnap.AirPressure );
|
||||
ApplyHunkVector<false>(AirVelocityX , newSnap.AirVelocityX );
|
||||
ApplyHunkVector<false>(AirVelocityY , newSnap.AirVelocityY );
|
||||
ApplyHunkVector<false>(AmbientHeat , newSnap.AmbientHeat );
|
||||
ApplyHunkVector<false>(GravVelocityX , newSnap.GravVelocityX );
|
||||
ApplyHunkVector<false>(GravVelocityY , newSnap.GravVelocityY );
|
||||
ApplyHunkVector<false>(GravValue , newSnap.GravValue );
|
||||
ApplyHunkVector<false>(GravMap , newSnap.GravMap );
|
||||
ApplyHunkVector<false>(BlockMap , newSnap.BlockMap );
|
||||
ApplyHunkVector<false>(ElecMap , newSnap.ElecMap );
|
||||
ApplyHunkVector<false>(FanVelocityX , newSnap.FanVelocityX );
|
||||
ApplyHunkVector<false>(FanVelocityY , newSnap.FanVelocityY );
|
||||
ApplyHunkVector<false>(WirelessData , newSnap.WirelessData );
|
||||
ApplySingleDiff<false>(signs , newSnap.signs );
|
||||
ApplySingleDiff<false>(Authors , newSnap.Authors );
|
||||
ApplyHunkVectorPtr<false>(PortalParticles, reinterpret_cast<uint32_t *>(&newSnap.PortalParticles[0]));
|
||||
ApplyHunkVectorPtr<false>(stickmen , reinterpret_cast<uint32_t *>(&newSnap.stickmen[0] ));
|
||||
|
||||
// * Slightly more interesting; apply the common hunk vector, copy the extra portion separaterly.
|
||||
ApplyHunkVectorPtr<false>(commonParticles, reinterpret_cast<uint32_t *>(&newSnap.Particles[0]));
|
||||
auto commonSize = oldSnap.Particles.size() - extraPartsOld.size();
|
||||
newSnap.Particles.resize(commonSize + extraPartsNew.size());
|
||||
std::copy(extraPartsNew.begin(), extraPartsNew.end(), newSnap.Particles.begin() + commonSize);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Snapshot> SnapshotDelta::Restore(const Snapshot &newSnap)
|
||||
{
|
||||
auto ptr = std::make_unique<Snapshot>(newSnap);
|
||||
auto &oldSnap = *ptr;
|
||||
ApplyHunkVector<true>(AirPressure , oldSnap.AirPressure );
|
||||
ApplyHunkVector<true>(AirVelocityX , oldSnap.AirVelocityX );
|
||||
ApplyHunkVector<true>(AirVelocityY , oldSnap.AirVelocityY );
|
||||
ApplyHunkVector<true>(AmbientHeat , oldSnap.AmbientHeat );
|
||||
ApplyHunkVector<true>(GravVelocityX , oldSnap.GravVelocityX );
|
||||
ApplyHunkVector<true>(GravVelocityY , oldSnap.GravVelocityY );
|
||||
ApplyHunkVector<true>(GravValue , oldSnap.GravValue );
|
||||
ApplyHunkVector<true>(GravMap , oldSnap.GravMap );
|
||||
ApplyHunkVector<true>(BlockMap , oldSnap.BlockMap );
|
||||
ApplyHunkVector<true>(ElecMap , oldSnap.ElecMap );
|
||||
ApplyHunkVector<true>(FanVelocityX , oldSnap.FanVelocityX );
|
||||
ApplyHunkVector<true>(FanVelocityY , oldSnap.FanVelocityY );
|
||||
ApplyHunkVector<true>(WirelessData , oldSnap.WirelessData );
|
||||
ApplySingleDiff<true>(signs , oldSnap.signs );
|
||||
ApplySingleDiff<true>(Authors , oldSnap.Authors );
|
||||
ApplyHunkVectorPtr<true>(PortalParticles, reinterpret_cast<uint32_t *>(&oldSnap.PortalParticles[0]));
|
||||
ApplyHunkVectorPtr<true>(stickmen , reinterpret_cast<uint32_t *>(&oldSnap.stickmen[0] ));
|
||||
|
||||
// * Slightly more interesting; apply the common hunk vector, copy the extra portion separaterly.
|
||||
ApplyHunkVectorPtr<true>(commonParticles, reinterpret_cast<uint32_t *>(&oldSnap.Particles[0]));
|
||||
auto commonSize = newSnap.Particles.size() - extraPartsNew.size();
|
||||
oldSnap.Particles.resize(commonSize + extraPartsOld.size());
|
||||
std::copy(extraPartsOld.begin(), extraPartsOld.end(), oldSnap.Particles.begin() + commonSize);
|
||||
|
||||
return ptr;
|
||||
}
|
76
src/simulation/SnapshotDelta.h
Normal file
76
src/simulation/SnapshotDelta.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "Snapshot.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
struct SnapshotDelta
|
||||
{
|
||||
template<class Item>
|
||||
struct Diff
|
||||
{
|
||||
Item oldItem, newItem;
|
||||
};
|
||||
|
||||
template<class Item>
|
||||
struct Hunk
|
||||
{
|
||||
int offset;
|
||||
std::vector<Diff<Item>> diffs;
|
||||
};
|
||||
|
||||
template<class Item>
|
||||
struct SingleDiff
|
||||
{
|
||||
bool valid = false;
|
||||
Diff<Item> diff;
|
||||
};
|
||||
|
||||
template<class Item>
|
||||
struct HalfHunk
|
||||
{
|
||||
int offset;
|
||||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
template<class Item>
|
||||
using HunkVector = std::vector<Hunk<Item>>;
|
||||
|
||||
template<class Item>
|
||||
struct HalfHunkVectorPair
|
||||
{
|
||||
std::vector<HalfHunk<Item>> oldHunks, newHunks;
|
||||
};
|
||||
|
||||
HunkVector<float> AirPressure;
|
||||
HunkVector<float> AirVelocityX;
|
||||
HunkVector<float> AirVelocityY;
|
||||
HunkVector<float> AmbientHeat;
|
||||
|
||||
HunkVector<uint32_t> commonParticles;
|
||||
std::vector<Particle> extraPartsOld, extraPartsNew;
|
||||
|
||||
HunkVector<float> GravVelocityX;
|
||||
HunkVector<float> GravVelocityY;
|
||||
HunkVector<float> GravValue;
|
||||
HunkVector<float> GravMap;
|
||||
|
||||
HunkVector<unsigned char> BlockMap;
|
||||
HunkVector<unsigned char> ElecMap;
|
||||
|
||||
HunkVector<float> FanVelocityX;
|
||||
HunkVector<float> FanVelocityY;
|
||||
|
||||
|
||||
HunkVector<uint32_t> PortalParticles;
|
||||
HunkVector<int> WirelessData;
|
||||
HunkVector<uint32_t> stickmen;
|
||||
SingleDiff<std::vector<sign>> signs;
|
||||
|
||||
SingleDiff<Json::Value> Authors;
|
||||
|
||||
static std::unique_ptr<SnapshotDelta> FromSnapshots(const Snapshot &oldSnap, const Snapshot &newSnap);
|
||||
std::unique_ptr<Snapshot> Forward(const Snapshot &oldSnap);
|
||||
std::unique_ptr<Snapshot> Restore(const Snapshot &newSnap);
|
||||
};
|
@ -11,6 +11,7 @@ simulation_files = files(
|
||||
'SimulationData.cpp',
|
||||
'ToolClasses.cpp',
|
||||
'Simulation.cpp',
|
||||
'SnapshotDelta.cpp',
|
||||
)
|
||||
|
||||
subdir('elements')
|
||||
|
Loading…
Reference in New Issue
Block a user