Refactor buffer undo tree

Store the undo tree as an array of undo nodes, instead of as a
pointer based tree.
This commit is contained in:
Maxime Coste 2018-05-02 21:31:44 +10:00
parent 4288f0fb3a
commit 74f90c1fc5
5 changed files with 91 additions and 108 deletions

View File

@ -62,8 +62,8 @@ static void apply_options(OptionManager& options, const ParsedLines& parsed_line
options.get_local_option("BOM").set(parsed_lines.bom); options.get_local_option("BOM").set(parsed_lines.bom);
} }
Buffer::HistoryNode::HistoryNode(size_t id, HistoryNode* parent) Buffer::HistoryNode::HistoryNode(HistoryId parent)
: id{id}, parent(parent), timepoint{Clock::now()} : parent{parent}, timepoint{Clock::now()}
{} {}
Buffer::Buffer(String name, Flags flags, StringView data, Buffer::Buffer(String name, Flags flags, StringView data,
@ -72,8 +72,9 @@ Buffer::Buffer(String name, Flags flags, StringView data,
m_name{(flags & Flags::File) ? real_path(parse_filename(name)) : std::move(name)}, m_name{(flags & Flags::File) ? real_path(parse_filename(name)) : std::move(name)},
m_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name}, m_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name},
m_flags{flags | Flags::NoUndo}, m_flags{flags | Flags::NoUndo},
m_history{m_next_history_id++, nullptr}, m_history_cursor{&m_history}, m_history{{HistoryId::Invalid}},
m_last_save_history_cursor{&m_history}, m_history_id{HistoryId::First},
m_last_save_history_id{HistoryId::First},
m_fs_timestamp{fs_timestamp.tv_sec, fs_timestamp.tv_nsec} m_fs_timestamp{fs_timestamp.tv_sec, fs_timestamp.tv_nsec}
{ {
ParsedLines parsed_lines = parse_lines(data); ParsedLines parsed_lines = parse_lines(data);
@ -252,9 +253,9 @@ void Buffer::reload(StringView data, timespec fs_timestamp)
if (not record_undo) if (not record_undo)
{ {
// Erase history about to be invalidated history // Erase history about to be invalidated history
m_history_cursor = &m_history; m_history_id = HistoryId::First;
m_last_save_history_cursor = &m_history; m_last_save_history_id = HistoryId::First;
m_history = HistoryNode{m_next_history_id++, nullptr}; m_history = {HistoryNode{HistoryId::Invalid}};
m_changes.push_back({ Change::Erase, {0,0}, line_count() }); m_changes.push_back({ Change::Erase, {0,0}, line_count() });
static_cast<BufferLines&>(m_lines) = std::move(parsed_lines.lines); static_cast<BufferLines&>(m_lines) = std::move(parsed_lines.lines);
@ -304,7 +305,7 @@ void Buffer::reload(StringView data, timespec fs_timestamp)
apply_options(options(), parsed_lines); apply_options(options(), parsed_lines);
m_last_save_history_cursor = m_history_cursor; m_last_save_history_id = m_history_id;
m_fs_timestamp = fs_timestamp; m_fs_timestamp = fs_timestamp;
} }
@ -316,13 +317,12 @@ void Buffer::commit_undo_group()
if (m_current_undo_group.empty()) if (m_current_undo_group.empty())
return; return;
auto* node = new HistoryNode{m_next_history_id++, m_history_cursor.get()}; const HistoryId id = next_history_id();
node->undo_group = std::move(m_current_undo_group); m_history.push_back({m_history_id});
m_history.back().undo_group = std::move(m_current_undo_group);
m_current_undo_group.clear(); m_current_undo_group.clear();
current_history_node().redo_child = id;
m_history_cursor->childs.emplace_back(node); m_history_id = id;
m_history_cursor->redo_child = node;
m_history_cursor = node;
} }
bool Buffer::undo(size_t count) bool Buffer::undo(size_t count)
@ -331,15 +331,15 @@ bool Buffer::undo(size_t count)
commit_undo_group(); commit_undo_group();
if (not m_history_cursor->parent) if (current_history_node().parent == HistoryId::Invalid)
return false; return false;
while (count-- != 0 and m_history_cursor->parent) while (count-- != 0 and current_history_node().parent != HistoryId::Invalid)
{ {
for (const Modification& modification : m_history_cursor->undo_group | reverse()) for (const Modification& modification : current_history_node().undo_group | reverse())
apply_modification(modification.inverse()); apply_modification(modification.inverse());
m_history_cursor = m_history_cursor->parent; m_history_id = current_history_node().parent;
} }
return true; return true;
@ -349,108 +349,82 @@ bool Buffer::redo(size_t count)
{ {
throw_if_read_only(); throw_if_read_only();
if (not m_history_cursor->redo_child) if (current_history_node().redo_child == HistoryId::Invalid)
return false; return false;
kak_assert(m_current_undo_group.empty()); kak_assert(m_current_undo_group.empty());
while (count-- != 0 and m_history_cursor->redo_child) while (count-- != 0 and current_history_node().redo_child != HistoryId::Invalid)
{ {
m_history_cursor = m_history_cursor->redo_child; m_history_id = current_history_node().redo_child;
for (const Modification& modification : m_history_cursor->undo_group) for (const Modification& modification : current_history_node().undo_group)
apply_modification(modification); apply_modification(modification);
} }
return true; return true;
} }
void Buffer::move_to(HistoryNode* history_node) bool Buffer::move_to(HistoryId id)
{ {
if (id >= next_history_id())
return false;
throw_if_read_only(); throw_if_read_only();
commit_undo_group(); commit_undo_group();
auto find_lowest_common_parent = [](HistoryNode* a, HistoryNode* b) { auto find_lowest_common_parent = [this](HistoryId a, HistoryId b) {
auto depth_of = [](HistoryNode* node) { auto depth_of = [this](HistoryId id) {
size_t depth = 0; size_t depth = 0;
for (; node->parent; node = node->parent.get()) for (; history_node(id).parent != HistoryId::Invalid; id = history_node(id).parent)
++depth; ++depth;
return depth; return depth;
}; };
auto depthA = depth_of(a), depthB = depth_of(b); auto depthA = depth_of(a), depthB = depth_of(b);
for (; depthA > depthB; --depthA) for (; depthA > depthB; --depthA)
a = a->parent.get(); a = history_node(a).parent;
for (; depthB > depthA; --depthB) for (; depthB > depthA; --depthB)
b = b->parent.get(); b = history_node(b).parent;
while (a != b) while (a != b)
{ {
a = a->parent.get(); a = history_node(a).parent;
b = b->parent.get(); b = history_node(b).parent;
} }
kak_assert(a == b and a != nullptr); kak_assert(a == b and a != HistoryId::Invalid);
return a; return a;
}; };
auto parent = find_lowest_common_parent(m_history_cursor.get(), history_node); auto parent = find_lowest_common_parent(m_history_id, id);
// undo up to common parent // undo up to common parent
for (auto it = m_history_cursor.get(); it != parent; it = it->parent.get()) for (auto id = m_history_id; id != parent; id = history_node(id).parent)
{ {
for (const Modification& modification : it->undo_group | reverse()) for (const Modification& modification : history_node(id).undo_group | reverse())
apply_modification(modification.inverse()); apply_modification(modification.inverse());
} }
static void (*apply_from_parent)(Buffer&, HistoryNode*, HistoryNode*) = static void (*apply_from_parent)(Buffer&, HistoryId, HistoryId) =
[](Buffer& buffer, HistoryNode* parent, HistoryNode* node) { [](Buffer& buffer, HistoryId parent, HistoryId id) {
if (node == parent) if (id == parent)
return; return;
apply_from_parent(buffer, parent, node->parent.get()); auto& node = buffer.history_node(id);
apply_from_parent(buffer, parent, node.parent);
node->parent->redo_child = node; buffer.history_node(node.parent).redo_child = id;
for (const Modification& modification : node->undo_group) for (const Modification& modification : node.undo_group)
buffer.apply_modification(modification); buffer.apply_modification(modification);
}; };
apply_from_parent(*this, parent, history_node); apply_from_parent(*this, parent, id);
m_history_cursor = history_node; m_history_id = id;
}
template<typename Func>
Buffer::HistoryNode* Buffer::find_history_node(HistoryNode* node, const Func& func)
{
if (func(node))
return node;
for (auto&& child : node->childs)
{
if (auto res = find_history_node(child.get(), func))
return res;
}
return nullptr;
}
bool Buffer::move_to(size_t history_id)
{
auto* target_node = find_history_node(&m_history, [history_id](auto* node)
{ return node->id == history_id; });
if (not target_node)
return false;
move_to(target_node);
return true; return true;
} }
size_t Buffer::current_history_id() const noexcept
{
return m_history_cursor->id;
}
void Buffer::check_invariant() const void Buffer::check_invariant() const
{ {
#ifdef KAK_DEBUG #ifdef KAK_DEBUG
@ -623,7 +597,7 @@ BufferCoord Buffer::replace(BufferCoord begin, BufferCoord end, StringView conte
bool Buffer::is_modified() const bool Buffer::is_modified() const
{ {
return m_flags & Flags::File and return m_flags & Flags::File and
(m_history_cursor != m_last_save_history_cursor or (m_history_id != m_last_save_history_id or
not m_current_undo_group.empty()); not m_current_undo_group.empty());
} }
@ -633,7 +607,7 @@ void Buffer::notify_saved()
commit_undo_group(); commit_undo_group();
m_flags &= ~Flags::New; m_flags &= ~Flags::New;
m_last_save_history_cursor = m_history_cursor; m_last_save_history_id = m_history_id;
m_fs_timestamp = get_fs_timestamp(m_name); m_fs_timestamp = get_fs_timestamp(m_name);
} }
@ -728,9 +702,9 @@ void Buffer::run_hook_in_own_context(StringView hook_name, StringView param, Str
Optional<BufferCoord> Buffer::last_modification_coord() const Optional<BufferCoord> Buffer::last_modification_coord() const
{ {
if (m_history_cursor.get() == &m_history) if (m_history_id == HistoryId::First)
return {}; return {};
return m_history_cursor->undo_group.back().coord; return current_history_node().undo_group.back().coord;
} }
String Buffer::debug_description() const String Buffer::debug_description() const
@ -739,14 +713,9 @@ String Buffer::debug_description() const
for (auto& line : m_lines) for (auto& line : m_lines)
content_size += (int)line->strview().length(); content_size += (int)line->strview().length();
static size_t (*count_mem)(const HistoryNode&) = [](const HistoryNode& node) { const size_t additional_size = accumulate(m_history, 0, [](size_t s, auto&& history) {
size_t size = node.undo_group.size() * sizeof(Modification); return sizeof(history) + history.undo_group.size() * sizeof(Modification) + s;
for (auto& child : node.childs) }) + m_changes.size() * sizeof(Change);
size += count_mem(*child);
return size;
};
const size_t additional_size = count_mem(m_history) +
m_changes.size() * sizeof(Change);
return format("{}\nFlags: {}{}{}{}\nUsed mem: content={} additional={}\n", return format("{}\nFlags: {}{}{}{}\nUsed mem: content={} additional={}\n",
display_name(), display_name(),
@ -860,7 +829,7 @@ UnitTest test_undo{[]()
kak_assert(buffer[1_line] == "mais que fais la police\n"); kak_assert(buffer[1_line] == "mais que fais la police\n");
kak_assert(buffer[2_line] == "foo\n"); kak_assert(buffer[2_line] == "foo\n");
buffer.move_to(3); buffer.move_to((Buffer::HistoryId)3);
kak_assert((int)buffer.line_count() == 5); kak_assert((int)buffer.line_count() == 5);
kak_assert(buffer[0_line] == "allo ?\n"); kak_assert(buffer[0_line] == "allo ?\n");
kak_assert(buffer[1_line] == "mais que fais la police\n"); kak_assert(buffer[1_line] == "mais que fais la police\n");
@ -868,13 +837,27 @@ UnitTest test_undo{[]()
kak_assert(buffer[3_line] == " hein ?\n"); kak_assert(buffer[3_line] == " hein ?\n");
kak_assert(buffer[4_line] == " youpi\n"); kak_assert(buffer[4_line] == " youpi\n");
buffer.move_to(4); buffer.move_to((Buffer::HistoryId)4);
kak_assert((int)buffer.line_count() == 5); kak_assert((int)buffer.line_count() == 5);
kak_assert(buffer[0_line] == "allo ?\n"); kak_assert(buffer[0_line] == "allo ?\n");
kak_assert(buffer[1_line] == "mais que fais la police\n"); kak_assert(buffer[1_line] == "mais que fais la police\n");
kak_assert(buffer[2_line] == "mutch\n"); kak_assert(buffer[2_line] == "mutch\n");
kak_assert(buffer[3_line] == " hein ?\n"); kak_assert(buffer[3_line] == " hein ?\n");
kak_assert(buffer[4_line] == " youpi\n"); kak_assert(buffer[4_line] == " youpi\n");
buffer.move_to(Buffer::HistoryId::First);
kak_assert((int)buffer.line_count() == 4);
kak_assert(buffer[0_line] == "allo ?\n");
kak_assert(buffer[1_line] == "mais que fais la police\n");
kak_assert(buffer[2_line] == " hein ?\n");
kak_assert(buffer[3_line] == " youpi\n");
kak_assert(not buffer.undo());
buffer.move_to((Buffer::HistoryId)5);
kak_assert(not buffer.redo());
buffer.move_to((Buffer::HistoryId)6);
kak_assert(not buffer.redo());
}}; }};
} }

View File

@ -119,6 +119,8 @@ public:
}; };
friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; } friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }
enum class HistoryId : size_t { First = 0, Invalid = (size_t)-1 };
Buffer(String name, Flags flags, StringView data = {}, Buffer(String name, Flags flags, StringView data = {},
timespec fs_timestamp = InvalidTime); timespec fs_timestamp = InvalidTime);
Buffer(const Buffer&) = delete; Buffer(const Buffer&) = delete;
@ -142,9 +144,9 @@ public:
void commit_undo_group(); void commit_undo_group();
bool undo(size_t count = 1); bool undo(size_t count = 1);
bool redo(size_t count = 1); bool redo(size_t count = 1);
bool move_to(size_t history_id); bool move_to(HistoryId id);
size_t current_history_id() const noexcept; HistoryId current_history_id() const noexcept { return m_history_id; }
size_t next_history_id() const noexcept { return m_next_history_id; } HistoryId next_history_id() const noexcept { return (HistoryId)m_history.size(); }
String string(BufferCoord begin, BufferCoord end) const; String string(BufferCoord begin, BufferCoord end) const;
StringView substr(BufferCoord begin, BufferCoord end) const; StringView substr(BufferCoord begin, BufferCoord end) const;
@ -256,27 +258,25 @@ private:
using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>; using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>;
struct HistoryNode : SafeCountable, UseMemoryDomain<MemoryDomain::BufferMeta> struct HistoryNode : UseMemoryDomain<MemoryDomain::BufferMeta>
{ {
HistoryNode(size_t id, HistoryNode* parent); HistoryNode(HistoryId parent);
UndoGroup undo_group; HistoryId parent;
Vector<std::unique_ptr<HistoryNode>, MemoryDomain::BufferMeta> childs; HistoryId redo_child = HistoryId::Invalid;
SafePtr<HistoryNode> parent;
HistoryNode* redo_child = nullptr; // not a SafePtr to avoid lifetime issues between this and childs
size_t id;
TimePoint timepoint; TimePoint timepoint;
UndoGroup undo_group;
}; };
size_t m_next_history_id = 0; Vector<HistoryNode> m_history;
HistoryNode m_history; HistoryId m_history_id = HistoryId::Invalid;
SafePtr<HistoryNode> m_history_cursor; HistoryId m_last_save_history_id = HistoryId::Invalid;
SafePtr<HistoryNode> m_last_save_history_cursor;
UndoGroup m_current_undo_group; UndoGroup m_current_undo_group;
void move_to(HistoryNode* history_node); HistoryNode& history_node(HistoryId id) { return m_history[(size_t)id]; }
const HistoryNode& history_node(HistoryId id) const { return m_history[(size_t)id]; }
template<typename Func> HistoryNode* find_history_node(HistoryNode* node, const Func& func); HistoryNode& current_history_node() { return m_history[(size_t)m_history_id]; }
const HistoryNode& current_history_node() const { return m_history[(size_t)m_history_id]; }
Vector<Change, MemoryDomain::BufferMeta> m_changes; Vector<Change, MemoryDomain::BufferMeta> m_changes;

View File

@ -1262,7 +1262,7 @@ public:
{ {
context().buffer().commit_undo_group(); context().buffer().commit_undo_group();
context().print_status({ format("committed change #{}", context().print_status({ format("committed change #{}",
context().buffer().current_history_id()), (size_t)context().buffer().current_history_id()),
context().faces()["Information"] }); context().faces()["Information"] });
} }
else if (key == ctrl('v')) else if (key == ctrl('v'))

View File

@ -104,7 +104,7 @@ static const EnvVarDesc builtin_env_vars[] = { {
}, { }, {
"history_id", false, "history_id", false,
[](StringView name, const Context& context) -> String [](StringView name, const Context& context) -> String
{ return to_string(context.buffer().current_history_id()); } { return to_string((size_t)context.buffer().current_history_id()); }
}, { }, {
"selection", false, "selection", false,
[](StringView name, const Context& context) [](StringView name, const Context& context)

View File

@ -1905,9 +1905,9 @@ void move_in_history(Context& context, NormalParams params)
Buffer& buffer = context.buffer(); Buffer& buffer = context.buffer();
size_t timestamp = buffer.timestamp(); size_t timestamp = buffer.timestamp();
const int count = std::max(1, params.count); const int count = std::max(1, params.count);
const int history_id = (int)buffer.current_history_id() + direction * count; const int history_id = (size_t)buffer.current_history_id() + direction * count;
const int max_history_id = (int)buffer.next_history_id() - 1; const int max_history_id = (int)buffer.next_history_id() - 1;
if (buffer.move_to((size_t)history_id)) if (buffer.move_to((Buffer::HistoryId)history_id))
{ {
auto ranges = compute_modified_ranges(buffer, timestamp); auto ranges = compute_modified_ranges(buffer, timestamp);
if (not ranges.empty()) if (not ranges.empty())