Undo/Redo serialization extension:

If an object indicates a valid timestamp, then the timestamp
is relied upon to not serialize the object data if the timestamp
of the same object on the undo/redo stack matches.
This commit is contained in:
Vojtech Bubnik 2020-09-23 12:18:39 +02:00
parent 230dbb7394
commit 056c46d01f
2 changed files with 49 additions and 8 deletions

View file

@ -49,7 +49,12 @@ private:
class ObjectBase class ObjectBase
{ {
public: public:
ObjectID id() const { return m_id; } ObjectID id() const { return m_id; }
// Return an optional timestamp of this object.
// If the timestamp returned is non-zero, then the serialization framework will
// only save this object on the Undo/Redo stack if the timestamp is different
// from the timestmap of the object at the top of the Undo / Redo stack.
virtual uint64_t timestamp() const { return 0; }
protected: protected:
// Constructors to be only called by derived classes. // Constructors to be only called by derived classes.
@ -59,7 +64,7 @@ protected:
// by an existing ID copied from elsewhere. // by an existing ID copied from elsewhere.
ObjectBase(int) : m_id(ObjectID(0)) {} ObjectBase(int) : m_id(ObjectID(0)) {}
// The class tree will have virtual tables and type information. // The class tree will have virtual tables and type information.
virtual ~ObjectBase() {} virtual ~ObjectBase() = default;
// Use with caution! // Use with caution!
void set_new_unique_id() { m_id = generate_new_id(); } void set_new_unique_id() { m_id = generate_new_id(); }

View file

@ -307,7 +307,11 @@ private:
size_t size; size_t size;
char data[1]; char data[1];
// The serialized data matches the data stored here.
bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; } bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; }
// The timestamp matches the timestamp serialized in the data stored here.
bool matches_timestamp(uint64_t timestamp) { assert(timestamp > 0); assert(this->size > 8); return memcmp(this->data, &timestamp, 8) == 0; }
}; };
Interval m_interval; Interval m_interval;
@ -350,7 +354,8 @@ public:
size_t size() const { return m_data->size; } size_t size() const { return m_data->size; }
size_t refcnt() const { return m_data->refcnt; } size_t refcnt() const { return m_data->refcnt; }
bool matches(const std::string& data) { return m_data->matches(data); } bool matches(const std::string& data) { return m_data->matches(data); }
size_t memsize() const { bool matches_timestamp(uint64_t timestamp) { return m_data->matches_timestamp(timestamp); }
size_t memsize() const {
return m_data->refcnt == 1 ? return m_data->refcnt == 1 ?
// Count just the size of the snapshot data. // Count just the size of the snapshot data.
m_data->size : m_data->size :
@ -398,6 +403,27 @@ public:
return memsize; return memsize;
} }
// If an object provides a reliable timestamp and the object serializes the timestamp first,
// then we may just check the validity of the timestamp against the last snapshot without
// having to serialize the whole object. This reduces the amount of serialization and memcmp
// when taking a snapshot.
bool try_save_timestamp(size_t active_snapshot_time, size_t current_time, uint64_t timestamp) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
if (! m_history.empty() && m_history.back().matches_timestamp(timestamp)) {
if (m_history.back().end() < active_snapshot_time)
// Share the previous data by reference counting.
m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back());
else {
assert(m_history.back().end() == active_snapshot_time);
// Just extend the last interval using the old data.
m_history.back().extend_end(current_time + 1);
}
return true;
}
// The timestamp is not valid, the caller has to call this->save() with the serialized data.
return false;
}
void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
if (m_history.empty() || m_history.back().end() < active_snapshot_time) { if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
@ -749,13 +775,23 @@ template<typename T> ObjectID StackImpl::save_mutable_object(const T &object)
if (it_object_history == m_objects.end()) if (it_object_history == m_objects.end())
it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr<MutableObjectHistory<T>>(new MutableObjectHistory<T>()))); it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr<MutableObjectHistory<T>>(new MutableObjectHistory<T>())));
auto *object_history = static_cast<MutableObjectHistory<T>*>(it_object_history->second.get()); auto *object_history = static_cast<MutableObjectHistory<T>*>(it_object_history->second.get());
// Then serialize the object into a string. bool needs_to_save = true;
std::ostringstream oss;
{ {
Slic3r::UndoRedo::OutputArchive archive(*this, oss); // If the timestamp returned is non zero, then it is considered reliable.
archive(object); // The caller is supposed to serialize the timestamp first.
uint64_t timestamp = object.timestamp();
if (timestamp > 0)
needs_to_save = ! object_history->try_save_timestamp(m_active_snapshot_time, m_current_time, timestamp);
}
if (needs_to_save) {
// Serialize the object into a string.
std::ostringstream oss;
{
Slic3r::UndoRedo::OutputArchive archive(*this, oss);
archive(object);
}
object_history->save(m_active_snapshot_time, m_current_time, oss.str());
} }
object_history->save(m_active_snapshot_time, m_current_time, oss.str());
return object.id(); return object.id();
} }