From be27b597aa3e9fdeec42f1d736769c699ee14efb Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:32:00 +0200 Subject: [PATCH 1/8] `ReaderMapping`: Clear arrays only before inserting new items When reading an array, it is only cleared right before new items are going to be added, instead of it being done before an attempt to read the value has been made. --- src/util/reader_mapping.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/util/reader_mapping.cpp b/src/util/reader_mapping.cpp index 2534d14f0c7..157f0cbbad2 100644 --- a/src/util/reader_mapping.cpp +++ b/src/util/reader_mapping.cpp @@ -146,6 +146,7 @@ ReaderMapping::get(const char* key, std::string& value, const std::optionalas_array(); \ for (size_t i = 1; i < item.size(); ++i) \ { \ @@ -159,7 +160,6 @@ bool ReaderMapping::get(const char* key, std::vector& value, const std::optional>& default_value) const { - value.clear(); GET_VALUES_MACRO("bool", is_boolean, as_bool) } @@ -167,7 +167,6 @@ bool ReaderMapping::get(const char* key, std::vector& value, const std::optional>& default_value) const { - value.clear(); GET_VALUES_MACRO("int", is_integer, as_int) } @@ -176,7 +175,6 @@ bool ReaderMapping::get(const char* key, std::vector& value, const std::optional>& default_value) const { - value.clear(); GET_VALUES_MACRO("float", is_real, as_float) } @@ -184,7 +182,6 @@ bool ReaderMapping::get(const char* key, std::vector& value, const std::optional>& default_value) const { - value.clear(); GET_VALUES_MACRO("string", is_string, as_string) } @@ -192,7 +189,6 @@ bool ReaderMapping::get(const char* key, std::vector& value, const std::optional>& default_value) const { - value.clear(); GET_VALUES_MACRO("unsigned int", is_integer, as_int) } From f65b0c37a1db8d4a27cdd63e18e71cad871d15b1 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:45:52 +0200 Subject: [PATCH 2/8] `GameObject` change undo/redo refactor to track different options `GameObject`, instead of comparing all options against each other, now saves the state of and compares individual ones, so the change data is more memory-efficient. This way of tracking object changes also allows for proper undo/redo tracking with multiple remote users. `GameObjectState` was renamed to `GameObjectChange`, since it does not always preserve all object options anymore. TODO: Proper tilemap tile-change tracking --- src/editor/object_option.cpp | 256 ++++++++++++++++++++++++--- src/editor/object_option.hpp | 95 +++++++--- src/editor/object_settings.cpp | 67 +++++++ src/editor/object_settings.hpp | 21 ++- src/object/tilemap.cpp | 11 ++ src/object/tilemap.hpp | 2 + src/supertux/game_object.cpp | 24 +-- src/supertux/game_object.hpp | 5 +- src/supertux/game_object_change.cpp | 92 ++++++++++ src/supertux/game_object_change.hpp | 70 ++++++++ src/supertux/game_object_manager.cpp | 219 +++++++++++++++++++---- src/supertux/game_object_manager.hpp | 47 +++-- src/util/reader_mapping.cpp | 9 + src/util/reader_mapping.hpp | 2 + src/util/uid.hpp | 5 + src/util/writer.cpp | 7 + src/util/writer.hpp | 3 + src/video/color.cpp | 4 +- src/video/color.hpp | 2 +- 19 files changed, 812 insertions(+), 129 deletions(-) create mode 100644 src/supertux/game_object_change.cpp create mode 100644 src/supertux/game_object_change.hpp diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index 06f70b2bfa7..af6e2afb57a 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -29,8 +29,12 @@ #include "gui/menu_object_select.hpp" #include "object/tilemap.hpp" #include "supertux/direction.hpp" +#include "supertux/game_object_factory.hpp" #include "supertux/moving_object.hpp" #include "util/gettext.hpp" +#include "util/log.hpp" +#include "util/reader_iterator.hpp" +#include "util/reader_mapping.hpp" #include "util/writer.hpp" #include "video/color.hpp" @@ -46,11 +50,56 @@ std::string fmt_to_string(const T& v) } // namespace +bool BaseObjectOption::s_allow_saving_defaults = false; + BaseObjectOption::BaseObjectOption(const std::string& text, const std::string& key, unsigned int flags) : m_text(text), m_key(key), - m_flags(flags) + m_flags(flags), + m_last_state() +{ +} + +std::string +BaseObjectOption::save() const +{ + std::ostringstream stream; + Writer writer(stream); + save(writer); + + return stream.str(); +} + +void +BaseObjectOption::save_state() +{ + s_allow_saving_defaults = true; + m_last_state = save(); + s_allow_saving_defaults = false; +} + +bool +BaseObjectOption::has_state_changed() const +{ + s_allow_saving_defaults = true; + const bool result = m_last_state != save(); + s_allow_saving_defaults = false; + + return result; +} + +void +BaseObjectOption::save_old_state(std::ostream& out) const +{ + out << m_last_state; +} + +void +BaseObjectOption::save_new_state(Writer& writer) const { + s_allow_saving_defaults = true; + save(writer); + s_allow_saving_defaults = false; } template @@ -74,11 +123,17 @@ BoolObjectOption::add_to_menu(Menu& menu) const menu.add_toggle(-1, get_text(), m_value_pointer); } +void +BoolObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void BoolObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { writer.write(get_key(), *m_value_pointer); @@ -100,11 +155,17 @@ IntObjectOption::IntObjectOption(const std::string& text, int* pointer, const st { } +void +IntObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void IntObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { writer.write(get_key(), *m_value_pointer); @@ -130,11 +191,6 @@ LabelObjectOption::LabelObjectOption(const std::string& text, { } -void -LabelObjectOption::save(Writer& writer) const -{ -} - std::string LabelObjectOption::to_string() const { @@ -155,6 +211,13 @@ RectfObjectOption::RectfObjectOption(const std::string& text, Rectf* pointer, co { } +void +RectfObjectOption::parse(const ReaderMapping& reader) +{ + reader.get("width", m_width); + reader.get("height", m_height); +} + void RectfObjectOption::save(Writer& write) const { @@ -187,11 +250,17 @@ FloatObjectOption::FloatObjectOption(const std::string& text, float* pointer, co { } +void +FloatObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void FloatObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { writer.write(get_key(), *m_value_pointer); @@ -219,11 +288,19 @@ StringObjectOption::StringObjectOption(const std::string& text, std::string* poi { } +void +StringObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void StringObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if ((m_default_value && *m_default_value == *m_value_pointer) || m_value_pointer->empty()) { + if (!s_allow_saving_defaults && + ((m_default_value && *m_default_value == *m_value_pointer) || + m_value_pointer->empty())) { // skip } else { writer.write(get_key(), *m_value_pointer, (get_flags() & OPTION_TRANSLATABLE)); @@ -251,11 +328,19 @@ StringMultilineObjectOption::StringMultilineObjectOption(const std::string& text { } +void +StringMultilineObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void StringMultilineObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if ((m_default_value && *m_default_value == *m_value_pointer) || m_value_pointer->empty()) { + if (!s_allow_saving_defaults && + ((m_default_value && *m_default_value == *m_value_pointer) || + m_value_pointer->empty())) { // skip } else { writer.write(get_key(), *m_value_pointer, (get_flags() & OPTION_TRANSLATABLE)); @@ -288,11 +373,17 @@ StringSelectObjectOption::StringSelectObjectOption(const std::string& text, int* { } +void +StringSelectObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void StringSelectObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { writer.write(get_key(), *m_value_pointer); @@ -333,13 +424,28 @@ EnumObjectOption::EnumObjectOption(const std::string& text, int* pointer, { } +void +EnumObjectOption::parse(const ReaderMapping& reader) +{ + std::string symbol; + if (reader.get(get_key().c_str(), symbol)) + { + int i = 0; + while (i < static_cast(m_symbols.size()) && m_symbols[i] != symbol) + i++; + + if (0 <= i && i < static_cast(m_symbols.size())) + *m_value_pointer = i; + } +} + void EnumObjectOption::save(Writer& writer) const { - if (0 <= *m_value_pointer && *m_value_pointer < int(m_symbols.size()) && + if (0 <= *m_value_pointer && *m_value_pointer < static_cast(m_symbols.size()) && !get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { writer.write(get_key(), m_symbols[*m_value_pointer]); @@ -350,7 +456,7 @@ EnumObjectOption::save(Writer& writer) const std::string EnumObjectOption::to_string() const { - if (0 <= *m_value_pointer && *m_value_pointer < int(m_labels.size())) { + if (0 <= *m_value_pointer && *m_value_pointer < static_cast(m_labels.size())) { return m_labels[*m_value_pointer]; } else { return _("invalid"); @@ -373,11 +479,17 @@ ScriptObjectOption::ScriptObjectOption(const std::string& text, std::string* poi { } +void +ScriptObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void ScriptObjectOption::save(Writer& writer) const { auto& value = *m_value_pointer; - if (!value.empty()) + if (s_allow_saving_defaults || !value.empty()) { if (!get_key().empty()) { writer.write(get_key(), value); @@ -415,10 +527,16 @@ FileObjectOption::FileObjectOption(const std::string& text, std::string* pointer { } +void +FileObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void FileObjectOption::save(Writer& writer) const { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { auto& value = *m_value_pointer; @@ -452,11 +570,19 @@ ColorObjectOption::ColorObjectOption(const std::string& text, Color* pointer, co { } +void +ColorObjectOption::parse(const ReaderMapping& reader) +{ + std::vector v_color; + if (reader.get(get_key().c_str(), v_color)) + *m_value_pointer = Color(v_color, m_use_alpha); +} + void ColorObjectOption::save(Writer& writer) const { if (!get_key().empty()) { - if (m_default_value && *m_default_value == *m_value_pointer) { + if (!s_allow_saving_defaults && m_default_value && *m_default_value == *m_value_pointer) { // skip } else { auto vec = m_value_pointer->toVector(); @@ -489,6 +615,34 @@ ObjectSelectObjectOption::ObjectSelectObjectOption(const std::string& text, std: { } +void +ObjectSelectObjectOption::parse(const ReaderMapping& reader) +{ + std::optional objects_mapping; + if (reader.get(get_key().c_str(), objects_mapping)) + { + m_value_pointer->clear(); + + auto iter = objects_mapping->get_iter(); + while (iter.next()) + { + try + { + auto obj = GameObjectFactory::instance().create(iter.get_key(), iter.as_mapping()); + if (m_add_object_function) + m_add_object_function(std::move(obj)); + else + m_value_pointer->push_back(std::move(obj)); + } + catch (const std::exception& err) + { + log_warning << "Error adding object select option object '" << iter.get_key() + << "': " << err.what() << std::endl; + } + } + } +} + void ObjectSelectObjectOption::save(Writer& writer) const { @@ -532,17 +686,22 @@ ObjectSelectObjectOption::add_to_menu(Menu& menu) const TilesObjectOption::TilesObjectOption(const std::string& text, TileMap* tilemap, const std::string& key, unsigned int flags) : - ObjectOption(text, key, flags), - m_tilemap(tilemap) + ObjectOption(text, key, flags, tilemap) { } +void +TilesObjectOption::parse(const ReaderMapping& reader) +{ + m_value_pointer->parse_tiles(reader); +} + void TilesObjectOption::save(Writer& write) const { - write.write("width", m_tilemap->get_width()); - write.write("height", m_tilemap->get_height()); - write.write("tiles", m_tilemap->get_tiles(), m_tilemap->get_width()); + write.write("width", m_value_pointer->get_width()); + write.write("height", m_value_pointer->get_height()); + write.write("tiles", m_value_pointer->get_tiles(), m_value_pointer->get_width()); } std::string @@ -562,6 +721,12 @@ PathObjectOption::PathObjectOption(const std::string& text, Path* path, const st { } +void +PathObjectOption::parse(const ReaderMapping& reader) +{ + m_value_pointer->read(reader); +} + void PathObjectOption::save(Writer& write) const { @@ -586,6 +751,12 @@ PathRefObjectOption::PathRefObjectOption(const std::string& text, PathObject& ta { } +void +PathRefObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), m_path_ref); +} + void PathRefObjectOption::save(Writer& writer) const { @@ -612,6 +783,12 @@ SExpObjectOption::SExpObjectOption(const std::string& text, const std::string& k { } +void +SExpObjectOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void SExpObjectOption::save(Writer& writer) const { @@ -638,6 +815,19 @@ PathHandleOption::PathHandleOption(const std::string& text, PathWalker::Handle& { } +void +PathHandleOption::parse(const ReaderMapping& reader) +{ + std::optional handle_mapping; + if (reader.get(get_key().c_str(), handle_mapping)) + { + handle_mapping->get("scale_x", m_target.m_scalar_pos.x); + handle_mapping->get("scale_y", m_target.m_scalar_pos.y); + handle_mapping->get("offset_x", m_target.m_pixel_offset.x); + handle_mapping->get("offset_y", m_target.m_pixel_offset.y); + } +} + void PathHandleOption::save(Writer& writer) const { @@ -742,6 +932,12 @@ StringArrayOption::StringArrayOption(const std::string& text, const std::string& m_items(items) {} +void +StringArrayOption::parse(const ReaderMapping& reader) +{ + reader.get("strings", m_items); +} + void StringArrayOption::save(Writer& write) const { @@ -759,6 +955,12 @@ ListOption::ListOption(const std::string& text, const std::string& key, const st m_items(items) {} +void +ListOption::parse(const ReaderMapping& reader) +{ + reader.get(get_key().c_str(), *m_value_pointer); +} + void ListOption::save(Writer& writer) const { @@ -782,6 +984,14 @@ DirectionOption::DirectionOption(const std::string& text, Direction* value_ptr, Direction::RIGHT, Direction::UP, Direction::DOWN }; } +void +DirectionOption::parse(const ReaderMapping& reader) +{ + std::string dir_string; + if (reader.get(get_key().c_str(), dir_string)) + *m_value_pointer = string_to_dir(dir_string); +} + void DirectionOption::save(Writer& writer) const { diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index ede074fc842..bfa7f7d4b44 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -45,20 +45,33 @@ class GameObject; class Menu; class Path; class PathObject; +class ReaderMapping; class Rectf; class TileMap; class Writer; class BaseObjectOption { +protected: + /** If set, options with their default value set will be saved. */ + static bool s_allow_saving_defaults; + public: BaseObjectOption(const std::string& text, const std::string& key, unsigned int flags); virtual ~BaseObjectOption() = default; - virtual void save(Writer& write) const = 0; + virtual void parse(const ReaderMapping& reader) = 0; + virtual void save(Writer& writer) const = 0; virtual std::string to_string() const = 0; virtual void add_to_menu(Menu& menu) const = 0; + std::string save() const; + + virtual void save_state(); + bool has_state_changed() const; + virtual void save_old_state(std::ostream& out) const; + virtual void save_new_state(Writer& writer) const; + const std::string& get_key() const { return m_key; } const std::string& get_text() const { return m_text; } unsigned int get_flags() const { return m_flags; } @@ -68,6 +81,8 @@ class BaseObjectOption const std::string m_key; const unsigned int m_flags; + std::string m_last_state; + private: BaseObjectOption(const BaseObjectOption&) = delete; BaseObjectOption& operator=(const BaseObjectOption&) = delete; @@ -97,7 +112,8 @@ class BoolObjectOption final : public ObjectOption std::optional default_value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -116,7 +132,8 @@ class IntObjectOption final : public ObjectOption std::optional default_value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -134,7 +151,8 @@ class LabelObjectOption final : public ObjectOption<> LabelObjectOption(const std::string& text, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override {} + virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -149,7 +167,8 @@ class RectfObjectOption final : public ObjectOption RectfObjectOption(const std::string& text, Rectf* pointer, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -169,7 +188,8 @@ class FloatObjectOption final : public ObjectOption std::optional default_value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -188,7 +208,8 @@ class StringObjectOption final : public ObjectOption std::optional default_value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -207,7 +228,8 @@ class StringMultilineObjectOption final : public ObjectOption std::optional default_value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -226,7 +248,8 @@ class StringSelectObjectOption final : public ObjectOption std::optional default_value, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -248,7 +271,8 @@ class EnumObjectOption final : public ObjectOption std::optional default_value, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -268,7 +292,8 @@ class ScriptObjectOption final : public ObjectOption ScriptObjectOption(const std::string& text, std::string* pointer, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -288,7 +313,8 @@ class FileObjectOption final : public ObjectOption bool path_relative_to_basedir, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -310,7 +336,8 @@ class ColorObjectOption final : public ObjectOption std::optional default_value, bool use_alpha, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -330,7 +357,8 @@ class ObjectSelectObjectOption final : public ObjectOption)>& add_object_func, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -349,13 +377,11 @@ class TilesObjectOption final : public ObjectOption TilesObjectOption(const std::string& text, TileMap* tilemap, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; -private: - TileMap* m_tilemap; - private: TilesObjectOption(const TilesObjectOption&) = delete; TilesObjectOption& operator=(const TilesObjectOption&) = delete; @@ -367,7 +393,8 @@ class PathObjectOption final : public ObjectOption PathObjectOption(const std::string& text, Path* path, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -382,7 +409,8 @@ class PathRefObjectOption final : public ObjectOption PathRefObjectOption(const std::string& text, PathObject& target, const std::string& path_ref, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -399,7 +427,8 @@ class SExpObjectOption final : public ObjectOption public: SExpObjectOption(const std::string& text, const std::string& key, sexp::Value& value, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -414,7 +443,8 @@ class PathHandleOption final : public ObjectOption PathHandleOption(const std::string& text, PathWalker::Handle& handle, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -431,7 +461,8 @@ class RemoveObjectOption final : public ObjectOption<> public: RemoveObjectOption(); - virtual void save(Writer& write) const override {} + virtual void parse(const ReaderMapping& reader) override {} + virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -445,7 +476,8 @@ class TestFromHereOption final : public ObjectOption<> public: TestFromHereOption(); - virtual void save(Writer& write) const override {} + virtual void parse(const ReaderMapping& reader) override {} + virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -459,7 +491,8 @@ class ParticleEditorOption final : public ObjectOption<> public: ParticleEditorOption(); - virtual void save(Writer& write) const override {} + virtual void parse(const ReaderMapping& reader) override {} + virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -473,7 +506,8 @@ class ButtonOption final : public ObjectOption<> public: ButtonOption(const std::string& text, std::function callback); - virtual void save(Writer& write) const override {} + virtual void parse(const ReaderMapping& reader) override {} + virtual void save(Writer& writer) const override {} virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; @@ -490,7 +524,8 @@ class StringArrayOption final : public ObjectOption<> public: StringArrayOption(const std::string& text, const std::string& key, std::vector& items); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override { return "text-area"; } virtual void add_to_menu(Menu& menu) const override; @@ -507,7 +542,8 @@ class ListOption final : public ObjectOption public: ListOption(const std::string& text, const std::string& key, const std::vector& items, std::string* value_ptr); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override { return *m_value_pointer; } virtual void add_to_menu(Menu& menu) const override; @@ -526,7 +562,8 @@ class DirectionOption final : public ObjectOption std::vector possible_directions, const std::string& key, unsigned int flags); - virtual void save(Writer& write) const override; + virtual void parse(const ReaderMapping& reader) override; + virtual void save(Writer& writer) const override; virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index 45ee96e4ca2..54bac920de2 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -20,6 +20,7 @@ #include #include "util/gettext.hpp" +#include "util/log.hpp" #include "video/color.hpp" ObjectSettings::ObjectSettings(const std::string& name) : @@ -28,9 +29,25 @@ ObjectSettings::ObjectSettings(const std::string& name) : { } +ObjectSettings::ObjectSettings(ObjectSettings&& other) : + m_name(other.m_name), + m_options(std::move(other.m_options)) +{ +} + void ObjectSettings::add_option(std::unique_ptr option) { + if (!option->get_key().empty()) + { + // Make sure no option with the same key exists + assert(std::none_of(m_options.begin(), m_options.end(), + [key = option->get_key()](const auto& opt) + { + return key == opt->get_key(); + })); + } + m_options.push_back(std::move(option)); } @@ -392,4 +409,54 @@ ObjectSettings::remove(const std::string& key) m_options.end()); } +void +ObjectSettings::save_state() +{ + for (const auto& option : m_options) + option->save_state(); +} + +bool +ObjectSettings::has_state_changed() const +{ + for (const auto& option : m_options) + if (option->has_state_changed()) + return true; + + return false; +} + +void +ObjectSettings::parse(const ReaderMapping& reader) +{ + for (const auto& option : m_options) + { + try + { + option->parse(reader); + } + catch (const std::exception& err) + { + log_warning << "Error processing data for option '" << option->get_key() + << "': " << err.what() << std::endl; + } + } +} + +void +ObjectSettings::save_old_state(std::ostream& out) const +{ + for (const auto& option : m_options) + if (option->has_state_changed()) + option->save_old_state(out); +} + +void +ObjectSettings::save_new_state(Writer& writer) const +{ + for (const auto& option : m_options) + if (option->has_state_changed()) + option->save_new_state(writer); +} + /* EOF */ diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index b9ba078d970..e76e8376cbe 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -28,10 +28,14 @@ class Color; enum class Direction; class GameObject; class PathObject; +class ReaderMapping; enum class WalkMode; +class Writer; + namespace worldmap { enum class Direction; } // namespace worldmap + namespace sexp { class Value; } // namespace sexp @@ -40,7 +44,9 @@ class ObjectSettings final { public: ObjectSettings(const std::string& name); - ObjectSettings(ObjectSettings&&) = default; + ObjectSettings(ObjectSettings&& other); + + ObjectSettings& operator=(ObjectSettings&&) = default; const std::string& get_name() const { return m_name; } @@ -167,6 +173,19 @@ class ObjectSettings final /** Remove an option from the list, this is a hack */ void remove(const std::string& key); + /** Save the current states of all options. */ + void save_state(); + + /** Check all options for any with a changed state. */ + bool has_state_changed() const; + + /** Parse option properties. */ + void parse(const ReaderMapping& reader); + + /** Write the old/new states of all modified options. */ + void save_old_state(std::ostream& out) const; + void save_new_state(Writer& writer) const; + private: void add_option(std::unique_ptr option); diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index a464917ab22..3d708516e7a 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -156,6 +156,12 @@ TileMap::TileMap(const TileSet *tileset_, const ReaderMapping& reader) : m_effective_solid = m_real_solid; update_effective_solid(false); + parse_tiles(reader); +} + +void +TileMap::parse_tiles(const ReaderMapping& reader) +{ reader.get("width", m_width); reader.get("height", m_height); if (m_width < 0 || m_height < 0) { @@ -190,6 +196,11 @@ TileMap::TileMap(const TileSet *tileset_, const ReaderMapping& reader) : { log_info << "Tilemap '" << get_name() << "', z-pos '" << m_z_pos << "' is empty." << std::endl; } + + m_new_size_x = m_width; + m_new_size_y = m_height; + m_new_offset_x = 0; + m_new_offset_y = 0; } void diff --git a/src/object/tilemap.hpp b/src/object/tilemap.hpp index 16a3f4a0b43..c558820bf47 100644 --- a/src/object/tilemap.hpp +++ b/src/object/tilemap.hpp @@ -57,6 +57,8 @@ class TileMap final : public GameObject, TileMap(const TileSet *tileset, const ReaderMapping& reader); ~TileMap() override; + void parse_tiles(const ReaderMapping& reader); + virtual void finish_construction() override; static std::string class_name() { return "tilemap"; } diff --git a/src/supertux/game_object.cpp b/src/supertux/game_object.cpp index d52bb6d5e20..d245bcf8604 100644 --- a/src/supertux/game_object.cpp +++ b/src/supertux/game_object.cpp @@ -196,14 +196,14 @@ GameObject::save_state() if (!m_parent->undo_tracking_enabled()) { - m_last_state.clear(); + m_last_state.reset(); return; } - if (!track_state()) + if (!track_state() || m_last_state) return; - if (m_last_state.empty()) - m_last_state = save(); + m_last_state = get_settings(); + m_last_state->save_state(); } void @@ -214,21 +214,15 @@ GameObject::check_state() if (!m_parent->undo_tracking_enabled()) { - m_last_state.clear(); + m_last_state.reset(); return; } - if (!track_state()) + if (!track_state() || !m_last_state) return; - // If settings have changed, save the change. - if (!m_last_state.empty()) - { - if (m_last_state != save()) - { - m_parent->save_object_change(*this, m_last_state); - } - m_last_state.clear(); - } + // Save any option changes. + m_parent->save_object_change(*this, *m_last_state); + m_last_state.reset(); } void diff --git a/src/supertux/game_object.hpp b/src/supertux/game_object.hpp index e576d653c2d..7b4ce294ba5 100644 --- a/src/supertux/game_object.hpp +++ b/src/supertux/game_object.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "editor/object_settings.hpp" @@ -309,9 +310,9 @@ class GameObject : public ExposableClass /** this flag indicates if the object should be removed at the end of the frame */ bool m_scheduled_for_removal; - /** The object's data at the time of the last state save. + /** The object's settings at the time of the last state save. Used to check for changes that may have occured. */ - std::string m_last_state; + std::optional m_last_state; std::vector > m_components; diff --git a/src/supertux/game_object_change.cpp b/src/supertux/game_object_change.cpp new file mode 100644 index 00000000000..8ca144b8c88 --- /dev/null +++ b/src/supertux/game_object_change.cpp @@ -0,0 +1,92 @@ +// SuperTux +// Copyright (C) 2024 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "supertux/game_object_change.hpp" + +#include "util/log.hpp" +#include "util/reader_iterator.hpp" +#include "util/reader_mapping.hpp" +#include "util/writer.hpp" + +GameObjectChange::GameObjectChange(const std::string& name_, const UID& uid_, + const std::string& data_, const std::string& new_data_, + Action action_) : + name(name_), + uid(uid_), + data(data_), + new_data(new_data_), + action(action_) +{ +} + +GameObjectChange::GameObjectChange(const ReaderMapping& reader) : + name(), + uid(), + data(), + new_data(), + action() +{ + reader.get("name", name); + reader.get("uid", uid); + reader.get("data", data); + reader.get("action", reinterpret_cast(action)); +} + +void +GameObjectChange::save(Writer& writer) const +{ + writer.write("name", name); + writer.write("uid", uid); + writer.write("data", data); + writer.write("action", reinterpret_cast(action)); +} + + +GameObjectChanges::GameObjectChanges(const UID& uid_, std::vector objects_) : + uid(uid_), + objects(std::move(objects_)) +{ +} + +GameObjectChanges::GameObjectChanges(const ReaderMapping& reader) : + uid(), + objects() +{ + auto iter = reader.get_iter(); + while (iter.next()) + { + if (iter.get_key() != "object-change") + { + log_warning << "Unknown key '" << iter.get_key() << "' in GameObjectChanges data. Ignoring." << std::endl; + continue; + } + + objects.push_back(GameObjectChange(iter.as_mapping())); + } +} + +void +GameObjectChanges::save(Writer& writer) const +{ + for (const auto& change : objects) + { + writer.start_list("object-change"); + change.save(writer); + writer.end_list("object-change"); + } +} + +/* EOF */ diff --git a/src/supertux/game_object_change.hpp b/src/supertux/game_object_change.hpp new file mode 100644 index 00000000000..39c6b91d1be --- /dev/null +++ b/src/supertux/game_object_change.hpp @@ -0,0 +1,70 @@ +// SuperTux +// Copyright (C) 2024 Vankata453 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef HEADER_SUPERTUX_SUPERTUX_GAME_OBJECT_CHANGE_HPP +#define HEADER_SUPERTUX_SUPERTUX_GAME_OBJECT_CHANGE_HPP + +#include + +#include "util/uid.hpp" + +class ReaderMapping; +class Writer; + +/** Stores a change in a GameObject's state. */ +class GameObjectChange final +{ +public: + enum class Action + { + CREATE, + DELETE, + MODIFY + }; + +public: + GameObjectChange(const std::string& name, const UID& uid, + const std::string& data, const std::string& new_data, + Action action); + GameObjectChange(const ReaderMapping& reader); + + void save(Writer& writer) const; + +public: + std::string name; + UID uid; + std::string data; // Stores old data of changed object options + std::string new_data; // Stores new data of changed object options + Action action; // The action which triggered a state change +}; + +/** Stores multiple GameObjectChanges. */ +class GameObjectChanges final +{ +public: + GameObjectChanges(const UID& uid, std::vector objects); + GameObjectChanges(const ReaderMapping& reader); + + void save(Writer& writer) const; + +public: + UID uid; + std::vector objects; +}; + +#endif + +/* EOF */ diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index 52d33a26af7..8c170d77cfd 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -29,6 +29,9 @@ #include "object/tilemap.hpp" #include "supertux/game_object_factory.hpp" #include "supertux/moving_object.hpp" +#include "util/reader_document.hpp" +#include "util/reader_mapping.hpp" +#include "util/writer.hpp" bool GameObjectManager::s_draw_solids_only = false; @@ -283,7 +286,7 @@ GameObjectManager::flush_game_objects() // If object changes have been performed since last flush, push them to the undo stack. if (m_undo_tracking && !m_pending_change_stack.empty()) { - m_undo_stack.push_back({ m_change_uid_generator.next(), std::move(m_pending_change_stack) }); + m_undo_stack.emplace_back(m_change_uid_generator.next(), std::move(m_pending_change_stack)); m_redo_stack.clear(); undo_stack_cleanup(); } @@ -376,16 +379,93 @@ GameObjectManager::on_editor_save() m_last_saved_change = (m_undo_stack.empty() ? UID() : m_undo_stack.back().uid); } +void +GameObjectManager::apply_object_change(const GameObjectChange& change, bool track_undo) +{ + GameObject* object = get_object_by_uid(change.uid); + switch (change.action) + { + case GameObjectChange::Action::CREATE: + { + create_object_from_change(change, track_undo); + } + break; + + case GameObjectChange::Action::DELETE: + { + if (!object) + throw std::runtime_error("Object does not exist."); + + object->m_track_undo = track_undo; + object->remove_me(); + } + break; + + case GameObjectChange::Action::MODIFY: + { + if (!object) + throw std::runtime_error("Object does not exist."); + + auto settings = object->get_settings(); + if (track_undo) + settings.save_state(); + + parse_object_settings(settings, change.data); // Parse settings + object->after_editor_set(); + + if (track_undo) + save_object_change(*object, settings); + } + break; + + default: + break; + } +} + +void +GameObjectManager::apply_object_changes(const GameObjectChanges& changes, bool track_undo) +{ + for (const auto& change : changes.objects) + { + try + { + apply_object_change(change, track_undo); + } + catch (const std::exception& err) + { + log_warning << "Cannot process object state change for object with UID " + << change.uid << ": " << err.what() << std::endl; + } + } +} + void GameObjectManager::undo() { if (m_undo_stack.empty()) return; - ObjectChanges& changes = m_undo_stack.back(); + GameObjectChanges& changes = m_undo_stack.back(); - for (auto& obj_change : changes.objects) - process_object_change(obj_change); + auto it = changes.objects.begin(); + while (it != changes.objects.end()) + { + try + { + process_object_change(*it); + it++; + } + catch (const std::exception& err) + { + log_warning << "Cannot process object change: " << err.what() << std::endl; + it = changes.objects.erase(it); // Drop invalid changes + } + } - m_redo_stack.push_back(std::move(changes)); + if (!changes.objects.empty()) + { + // Changes have been reversed for redo + m_redo_stack.push_back(std::move(changes)); + } m_undo_stack.pop_back(); } @@ -393,62 +473,137 @@ void GameObjectManager::redo() { if (m_redo_stack.empty()) return; - ObjectChanges& changes = m_redo_stack.back(); + GameObjectChanges& changes = m_redo_stack.back(); - for (auto& obj_change : changes.objects) - process_object_change(obj_change); + auto it = changes.objects.begin(); + while (it != changes.objects.end()) + { + try + { + process_object_change(*it); + it++; + } + catch (const std::exception& err) + { + log_warning << "Cannot process object change: " << err.what() << std::endl; + it = changes.objects.erase(it); // Drop invalid changes + } + } - m_undo_stack.push_back(std::move(changes)); + if (!changes.objects.empty()) + { + // Changes have been reversed for undo + m_undo_stack.push_back(std::move(changes)); + } m_redo_stack.pop_back(); } void -GameObjectManager::create_object_from_change(const ObjectChange& change) +GameObjectManager::create_object_from_change(const GameObjectChange& change, bool track_undo) { auto object = GameObjectFactory::instance().create(change.name, change.data); - object->m_track_undo = false; + object->m_track_undo = track_undo; object->set_uid(change.uid); object->after_editor_set(); add_object(std::move(object)); } void -GameObjectManager::process_object_change(ObjectChange& change) +GameObjectManager::parse_object_settings(ObjectSettings& settings, const std::string& data) +{ + std::istringstream stream(data); + auto doc = ReaderDocument::from_stream(stream); + auto root = doc.get_root(); + if (root.get_name() != "supertux-game-object") + throw std::runtime_error("Data is not 'supertux-game-object'."); + + settings.parse(root.get_mapping()); +} + +std::string +GameObjectManager::save_object_settings_state(const ObjectSettings& settings, bool new_state) +{ + std::ostringstream stream; + Writer writer(stream); + + writer.start_list("supertux-game-object"); + if (new_state) + settings.save_new_state(writer); + else + settings.save_old_state(stream); + writer.end_list("supertux-game-object"); + + return stream.str(); +} + +void +GameObjectManager::process_object_change(GameObjectChange& change) { GameObject* object = get_object_by_uid(change.uid); - if (object) // Object exists, remove it. + switch (change.action) { - object->m_track_undo = false; - object->remove_me(); + case GameObjectChange::Action::CREATE: /** Object was added, remove it. */ + { + assert(object); - const std::string data = object->save(); + object->m_track_undo = false; + object->remove_me(); - // If settings have changed, re-create object with old settings. - if (!change.creation && change.data != data) - create_object_from_change(change); + // Prepare for redo + change.data = object->save(); + change.action = GameObjectChange::Action::DELETE; + } + break; - change.data = std::move(data); - } - else // Object doesn't exist, create it. - { - create_object_from_change(change); + case GameObjectChange::Action::DELETE: /** Object was deleted, create it. */ + { + create_object_from_change(change, false); + + // Prepare for redo + change.action = GameObjectChange::Action::CREATE; + } + break; + + case GameObjectChange::Action::MODIFY: /** Object was modified, revert settings. */ + { + assert(object); + + auto settings = object->get_settings(); + settings.save_state(); + + parse_object_settings(settings, change.data); // Parse old settings + object->after_editor_set(); + + // Prepare for redo + change.data = save_object_settings_state(settings, false); + change.new_data = save_object_settings_state(settings, true); + } + break; + + default: + break; } } void -GameObjectManager::save_object_change(GameObject& object, bool creation) +GameObjectManager::save_object_state(GameObject& object, GameObjectChange::Action action) { - if (m_undo_tracking && object.track_state() && object.m_track_undo) - m_pending_change_stack.push_back({ object.get_class_name(), object.get_uid(), object.save(), creation }); + if (object.track_state() && object.m_track_undo) + m_pending_change_stack.push_back({ object.get_class_name(), object.get_uid(), + object.save(), "", action }); object.m_track_undo = true; } void -GameObjectManager::save_object_change(GameObject& object, const std::string& data) +GameObjectManager::save_object_change(const GameObject& object, const ObjectSettings& settings) { - if (m_undo_tracking) - m_pending_change_stack.push_back({ object.get_class_name(), object.get_uid(), data, false }); + if (!settings.has_state_changed()) return; + + m_pending_change_stack.push_back({ object.get_class_name(), object.get_uid(), + save_object_settings_state(settings, false), + save_object_settings_state(settings, true), + GameObjectChange::Action::MODIFY }); } void @@ -489,13 +644,13 @@ GameObjectManager::this_before_object_add(GameObject& object) } } - save_object_change(object, true); + save_object_state(object, GameObjectChange::Action::CREATE); } void GameObjectManager::this_before_object_remove(GameObject& object) { - save_object_change(object); + save_object_state(object, GameObjectChange::Action::DELETE); { // By name: const std::string& name = object.get_name(); diff --git a/src/supertux/game_object_manager.hpp b/src/supertux/game_object_manager.hpp index 07a79f77dba..e2ac2c02fc0 100644 --- a/src/supertux/game_object_manager.hpp +++ b/src/supertux/game_object_manager.hpp @@ -28,6 +28,7 @@ #include #include "supertux/game_object.hpp" +#include "supertux/game_object_change.hpp" #include "util/uid_generator.hpp" class DrawingContext; @@ -273,14 +274,19 @@ class GameObjectManager : public ExposableClass void undo(); void redo(); - /** Save object change in the undo stack with given data. + /** Apply saved object changes. */ + void apply_object_change(const GameObjectChange& change, bool track_undo); + void apply_object_changes(const GameObjectChanges& changes, bool track_undo); + + /** Save object settings changes in the undo stack. Used to save an object's previous state before a change had occurred. */ - void save_object_change(GameObject& object, const std::string& data); + void save_object_change(const GameObject& object, const ObjectSettings& settings); /** Clear undo/redo stacks. */ void clear_undo_stack(); - /** Indicate if there are any object changes in the undo stack. */ + /** Indicate if there are any unsaved object changes in the undo stack. + @see m_last_saved_change */ bool has_object_changes() const; /** Called on editor level save. */ @@ -311,27 +317,20 @@ class GameObjectManager : public ExposableClass } private: - struct ObjectChange - { - std::string name; - UID uid; - std::string data; - bool creation; // If the change represents an object creation. - }; - struct ObjectChanges - { - UID uid; - std::vector objects; - }; - /** Create object from object change. */ - void create_object_from_change(const ObjectChange& change); + void create_object_from_change(const GameObjectChange& change, bool track_undo); + + /** Parse object settings ("supertux-game-object") from a string. */ + static void parse_object_settings(ObjectSettings& settings, const std::string& data); + + /** Save old or new state of object settings. */ + static std::string save_object_settings_state(const ObjectSettings& settings, bool new_state); - /** Process object change on undo/redo. */ - void process_object_change(ObjectChange& change); + /** Undo/redo object change. */ + void process_object_change(GameObjectChange& change); - /** Save object change in the undo stack. */ - void save_object_change(GameObject& object, bool creation = false); + /** Save object state in the undo stack. */ + void save_object_state(GameObject& object, GameObjectChange::Action action); void this_before_object_add(GameObject& object); void this_before_object_remove(GameObject& object); @@ -347,9 +346,9 @@ class GameObjectManager : public ExposableClass UIDGenerator m_change_uid_generator; bool m_undo_tracking; int m_undo_stack_size; - std::vector m_undo_stack; - std::vector m_redo_stack; - std::vector m_pending_change_stack; // Before a flush, any changes go here + std::vector m_undo_stack; + std::vector m_redo_stack; + std::vector m_pending_change_stack; // Before a flush, any changes go here UID m_last_saved_change; std::vector> m_gameobjects; diff --git a/src/util/reader_mapping.cpp b/src/util/reader_mapping.cpp index 157f0cbbad2..a7bcef0200e 100644 --- a/src/util/reader_mapping.cpp +++ b/src/util/reader_mapping.cpp @@ -45,6 +45,9 @@ ReaderMapping::get_iter() const const sexp::Value* ReaderMapping::get_item(const char* key) const { + if (!key || !key[0]) // Check whether key is valid and non-empty + return nullptr; + for (size_t i = 1; i < m_arr.size(); ++i) { auto const& pair = m_arr[i]; @@ -95,6 +98,12 @@ ReaderMapping::get(const char* key, uint32_t& value, const std::optional& default_value) const +{ + GET_VALUE_MACRO("uint32_t", is_integer, as_int) +} + bool ReaderMapping::get(const char* key, float& value, const std::optional& default_value) const { diff --git a/src/util/reader_mapping.hpp b/src/util/reader_mapping.hpp index 186f544bb50..02a4af3d9aa 100644 --- a/src/util/reader_mapping.hpp +++ b/src/util/reader_mapping.hpp @@ -21,6 +21,7 @@ #include #include "util/reader_iterator.hpp" +#include "util/uid.hpp" namespace sexp { class Value; @@ -43,6 +44,7 @@ class ReaderMapping final bool get(const char* key, bool& value, const std::optional& default_value = std::nullopt) const; bool get(const char* key, int& value, const std::optional& default_value = std::nullopt) const; bool get(const char* key, uint32_t& value, const std::optional& default_value = std::nullopt) const; + bool get(const char* key, UID& value, const std::optional& default_value = std::nullopt) const; bool get(const char* key, float& value, const std::optional& default_value = std::nullopt) const; bool get(const char* key, std::string& value, const std::optional& default_value = std::nullopt) const; diff --git a/src/util/uid.hpp b/src/util/uid.hpp index c7c58d7a1bd..d043a8f590b 100644 --- a/src/util/uid.hpp +++ b/src/util/uid.hpp @@ -55,6 +55,11 @@ class UID UID(const UID& other) = default; UID& operator=(const UID& other) = default; + inline UID& operator=(uint32_t value) { + m_value = value; + return *this; + } + inline operator bool() const { return m_value != 0; } diff --git a/src/util/writer.cpp b/src/util/writer.cpp index b8dd33dad24..725afc9771e 100644 --- a/src/util/writer.cpp +++ b/src/util/writer.cpp @@ -104,6 +104,13 @@ Writer::write(const std::string& name, float value) *out << '(' << name << ' ' << value << ")\n"; } +void +Writer::write(const std::string& name, const UID& uid) +{ + indent(); + *out << '(' << name << ' ' << uid << ")\n"; +} + /** This function is needed to properly resolve the overloaded write() function, without it the call write("foo", "bar") would call write(name, bool), not write(name, string, bool) */ diff --git a/src/util/writer.hpp b/src/util/writer.hpp index e374b62b2c8..273bbd40aaa 100644 --- a/src/util/writer.hpp +++ b/src/util/writer.hpp @@ -20,6 +20,8 @@ #include #include +#include "util/uid.hpp" + namespace sexp { class Value; } // namespace sexp @@ -38,6 +40,7 @@ class Writer final void write(const std::string& name, bool value); void write(const std::string& name, int value); void write(const std::string& name, float value); + void write(const std::string& name, const UID& uid); void write(const std::string& name, const char* value); void write(const std::string& name, const std::string& value, bool translatable = false); void write(const std::string& name, const std::vector& value); diff --git a/src/video/color.cpp b/src/video/color.cpp index 868e7c59d8f..244e59ece3b 100644 --- a/src/video/color.cpp +++ b/src/video/color.cpp @@ -45,7 +45,7 @@ Color::Color(float red_, float green_, float blue_, float alpha_) : assert(0 <= blue && blue <= 1.0f); } -Color::Color(const std::vector& vals) : +Color::Color(const std::vector& vals, bool use_alpha) : red(), green(), blue(), @@ -61,7 +61,7 @@ Color::Color(const std::vector& vals) : red = vals[0]; green = vals[1]; blue = vals[2]; - if (vals.size() > 3) + if (use_alpha && vals.size() > 3) alpha = vals[3]; else alpha = 1.0; diff --git a/src/video/color.hpp b/src/video/color.hpp index 87b13355f15..8490e133c95 100644 --- a/src/video/color.hpp +++ b/src/video/color.hpp @@ -95,7 +95,7 @@ class Color final Color(float red_, float green_, float blue_, float alpha_ = 1.0); - Color(const std::vector& vals); + Color(const std::vector& vals, bool use_alpha = true); bool operator==(const Color& other) const; bool operator!=(const Color& other) const; From 7c4a0f7abd76b316a95026dd760f893c6fab832b Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:47:02 +0200 Subject: [PATCH 3/8] Fix crash when performing undo/redo on deleted object Previously, an assertion was used for performing this check, which was the cause of the crash. It wasn't taken into account performing undo/redo on a remotely-deleted object. Additionally, the warning message now includes the class name of the object. --- src/supertux/game_object_manager.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index 8c170d77cfd..64245f867b4 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -394,7 +394,7 @@ GameObjectManager::apply_object_change(const GameObjectChange& change, bool trac case GameObjectChange::Action::DELETE: { if (!object) - throw std::runtime_error("Object does not exist."); + throw std::runtime_error("Object '" + change.name + "' does not exist."); object->m_track_undo = track_undo; object->remove_me(); @@ -404,7 +404,7 @@ GameObjectManager::apply_object_change(const GameObjectChange& change, bool trac case GameObjectChange::Action::MODIFY: { if (!object) - throw std::runtime_error("Object does not exist."); + throw std::runtime_error("Object '" + change.name + "' does not exist."); auto settings = object->get_settings(); if (track_undo) @@ -544,7 +544,8 @@ GameObjectManager::process_object_change(GameObjectChange& change) { case GameObjectChange::Action::CREATE: /** Object was added, remove it. */ { - assert(object); + if (!object) + throw std::runtime_error("Object '" + change.name + "' no longer exists."); object->m_track_undo = false; object->remove_me(); @@ -566,7 +567,8 @@ GameObjectManager::process_object_change(GameObjectChange& change) case GameObjectChange::Action::MODIFY: /** Object was modified, revert settings. */ { - assert(object); + if (!object) + throw std::runtime_error("Object '" + change.name + "' no longer exists."); auto settings = object->get_settings(); settings.save_state(); From 3d5d306de6be7c5f4389aa00a19c9f9d1ad905b7 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:11:46 +0200 Subject: [PATCH 4/8] `TileMap`: Proper tile change tracking `TileMap` tile changes are now stored in pairs, containing a tile array index and old/new tile, which takes its place. Allows for lighter storage in memory for undo/redo, as well as for lower-sized packets and proper remote tile undo/redo with editor remote level networking. --- src/editor/object_option.cpp | 84 +++++++++++++++++++++++++++- src/editor/object_option.hpp | 20 +++++++ src/editor/object_settings.cpp | 23 +++++++- src/editor/object_settings.hpp | 7 ++- src/object/tilemap.cpp | 21 +++++-- src/object/tilemap.hpp | 1 + src/supertux/game_object_manager.cpp | 2 +- 7 files changed, 145 insertions(+), 13 deletions(-) diff --git a/src/editor/object_option.cpp b/src/editor/object_option.cpp index af6e2afb57a..a25b044a88d 100644 --- a/src/editor/object_option.cpp +++ b/src/editor/object_option.cpp @@ -88,6 +88,12 @@ BaseObjectOption::has_state_changed() const return result; } +void +BaseObjectOption::parse_state(const ReaderMapping& reader) +{ + parse(reader); +} + void BaseObjectOption::save_old_state(std::ostream& out) const { @@ -684,9 +690,17 @@ ObjectSelectObjectOption::add_to_menu(Menu& menu) const }); } +TilesObjectOption::TilesState::TilesState() : + width(), + height(), + tiles() +{ +} + TilesObjectOption::TilesObjectOption(const std::string& text, TileMap* tilemap, const std::string& key, unsigned int flags) : - ObjectOption(text, key, flags, tilemap) + ObjectOption(text, key, flags, tilemap), + m_last_tiles_state() { } @@ -715,6 +729,74 @@ TilesObjectOption::add_to_menu(Menu& menu) const { } +void +TilesObjectOption::save_state() +{ + BaseObjectOption::save_state(); + + m_last_tiles_state.width = m_value_pointer->get_width(); + m_last_tiles_state.height = m_value_pointer->get_height(); + m_last_tiles_state.tiles = m_value_pointer->get_tiles(); +} + +void +TilesObjectOption::parse_state(const ReaderMapping& reader) +{ + parse(reader); + + std::vector tile_changes; // Array of pairs (index, old/new tile ID). + if (!reader.get("tile-changes", tile_changes)) + return; + + if (tile_changes.size() % 2 != 0) + throw std::runtime_error("'tile-changes' does not contain number pairs."); + + for (size_t i = 0; i < tile_changes.size(); i += 2) + m_value_pointer->change(tile_changes[i], static_cast(tile_changes[i + 1])); +} + +void +TilesObjectOption::save_old_state(std::ostream& out) const +{ + Writer writer(out); + save_tile_changes(writer, false); +} + +void +TilesObjectOption::save_new_state(Writer& writer) const +{ + save_tile_changes(writer, true); +} + +void +TilesObjectOption::save_tile_changes(Writer& writer, bool new_tiles) const +{ + writer.write("width", new_tiles ? m_value_pointer->get_width() : m_last_tiles_state.width); + writer.write("height", new_tiles ? m_value_pointer->get_height() : m_last_tiles_state.height); + + assert(!m_last_tiles_state.tiles.empty()); + const auto& tiles = m_value_pointer->get_tiles(); + + // Tiles have been resized. Save all tiles. + if (m_last_tiles_state.tiles.size() != tiles.size()) + { + writer.write("tiles", new_tiles ? tiles : m_last_tiles_state.tiles); + return; + } + + // Get and write old/new states of changed tiles in the array. + std::vector tile_changes; // Array of pairs (index, old/new tile ID). + for (uint32_t i = 0; i < static_cast(m_last_tiles_state.tiles.size()); i++) + { + if (m_last_tiles_state.tiles[i] != tiles[i]) + { + tile_changes.push_back(i); + tile_changes.push_back(new_tiles ? tiles[i] : m_last_tiles_state.tiles[i]); + } + } + writer.write("tile-changes", tile_changes); +} + PathObjectOption::PathObjectOption(const std::string& text, Path* path, const std::string& key, unsigned int flags) : ObjectOption(text, key, flags, path) diff --git a/src/editor/object_option.hpp b/src/editor/object_option.hpp index bfa7f7d4b44..2385921be40 100644 --- a/src/editor/object_option.hpp +++ b/src/editor/object_option.hpp @@ -69,6 +69,7 @@ class BaseObjectOption virtual void save_state(); bool has_state_changed() const; + virtual void parse_state(const ReaderMapping& reader); virtual void save_old_state(std::ostream& out) const; virtual void save_new_state(Writer& writer) const; @@ -382,6 +383,25 @@ class TilesObjectOption final : public ObjectOption virtual std::string to_string() const override; virtual void add_to_menu(Menu& menu) const override; + virtual void save_state() override; + virtual void parse_state(const ReaderMapping& reader) override; + virtual void save_old_state(std::ostream& out) const override; + virtual void save_new_state(Writer& writer) const override; + +private: + void save_tile_changes(Writer& writer, bool new_tiles) const; + +private: + struct TilesState final + { + TilesState(); + + int width; + int height; + std::vector tiles; + }; + TilesState m_last_tiles_state; + private: TilesObjectOption(const TilesObjectOption&) = delete; TilesObjectOption& operator=(const TilesObjectOption&) = delete; diff --git a/src/editor/object_settings.cpp b/src/editor/object_settings.cpp index 54bac920de2..964850e3971 100644 --- a/src/editor/object_settings.cpp +++ b/src/editor/object_settings.cpp @@ -409,6 +409,23 @@ ObjectSettings::remove(const std::string& key) m_options.end()); } +void +ObjectSettings::parse(const ReaderMapping& reader) +{ + for (const auto& option : m_options) + { + try + { + option->parse(reader); + } + catch (const std::exception& err) + { + log_warning << "Error processing data for option '" << option->get_key() + << "': " << err.what() << std::endl; + } + } +} + void ObjectSettings::save_state() { @@ -427,17 +444,17 @@ ObjectSettings::has_state_changed() const } void -ObjectSettings::parse(const ReaderMapping& reader) +ObjectSettings::parse_state(const ReaderMapping& reader) { for (const auto& option : m_options) { try { - option->parse(reader); + option->parse_state(reader); } catch (const std::exception& err) { - log_warning << "Error processing data for option '" << option->get_key() + log_warning << "Error processing state data for option '" << option->get_key() << "': " << err.what() << std::endl; } } diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index e76e8376cbe..c56828745d6 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -173,14 +173,17 @@ class ObjectSettings final /** Remove an option from the list, this is a hack */ void remove(const std::string& key); + /** Parse option properties from a previous state. */ + void parse(const ReaderMapping& reader); + /** Save the current states of all options. */ void save_state(); /** Check all options for any with a changed state. */ bool has_state_changed() const; - /** Parse option properties. */ - void parse(const ReaderMapping& reader); + /** Parse option properties from an alternative state. */ + void parse_state(const ReaderMapping& reader); /** Write the old/new states of all modified options. */ void save_old_state(std::ostream& out) const; diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 3d708516e7a..79ac8b6aa59 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -164,7 +164,8 @@ TileMap::parse_tiles(const ReaderMapping& reader) { reader.get("width", m_width); reader.get("height", m_height); - if (m_width < 0 || m_height < 0) { + if (m_width < 0 || m_height < 0) + { //throw std::runtime_error("Invalid/No width/height specified in tilemap."); m_width = 0; m_height = 0; @@ -172,13 +173,15 @@ TileMap::parse_tiles(const ReaderMapping& reader) resize(static_cast(Sector::get().get_width() / 32.0f), static_cast(Sector::get().get_height() / 32.0f)); m_editor_active = false; - } else { - if (!reader.get("tiles", m_tiles)) + } + else + { + reader.get("tiles", m_tiles); + if (m_tiles.empty()) throw std::runtime_error("No tiles in tilemap."); - if (int(m_tiles.size()) != m_width * m_height) { + if (static_cast(m_tiles.size()) != m_width * m_height) throw std::runtime_error("wrong number of tiles in tilemap."); - } } bool empty = true; @@ -710,6 +713,12 @@ TileMap::change(int x, int y, uint32_t newtile) m_tiles[y*m_width + x] = newtile; } +void +TileMap::change(int idx, uint32_t newtile) +{ + m_tiles[idx] = newtile; +} + void TileMap::change_at(const Vector& pos, uint32_t newtile) { @@ -1002,7 +1011,7 @@ TileMap::register_class(ssq::VM& vm) cls.addFunc("get_tile_id", &TileMap::get_tile_id); cls.addFunc("get_tile_id_at", &TileMap::get_tile_id_at); - cls.addFunc("change", &TileMap::change); + cls.addFunc("change", &TileMap::change); cls.addFunc("change_at", &TileMap::change_at); cls.addFunc("change_all", &TileMap::change_all); cls.addFunc("fade", &TileMap::fade); diff --git a/src/object/tilemap.hpp b/src/object/tilemap.hpp index c558820bf47..3e1f14ab2d2 100644 --- a/src/object/tilemap.hpp +++ b/src/object/tilemap.hpp @@ -189,6 +189,7 @@ class TileMap final : public GameObject, * @param int $newtile */ void change(int x, int y, uint32_t newtile); + void change(int idx, uint32_t newtile); /** * @scripting * @description Changes the tile at the given position (in-world coordinates) to ""newtile"". diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index 64245f867b4..d0d10ec46f3 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -517,7 +517,7 @@ GameObjectManager::parse_object_settings(ObjectSettings& settings, const std::st if (root.get_name() != "supertux-game-object") throw std::runtime_error("Data is not 'supertux-game-object'."); - settings.parse(root.get_mapping()); + settings.parse_state(root.get_mapping()); } std::string From 3de0fa8d7ce9360e47d005ad9f951e4081a33db7 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Mon, 21 Oct 2024 23:05:38 +0300 Subject: [PATCH 5/8] Fix crash un-doing creation of layer while hovering over it --- src/editor/layers_widget.cpp | 30 +++++++++++++++++++----------- src/editor/layers_widget.hpp | 2 ++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/editor/layers_widget.cpp b/src/editor/layers_widget.cpp index 61d9bf78f1d..98a330e863a 100644 --- a/src/editor/layers_widget.cpp +++ b/src/editor/layers_widget.cpp @@ -148,17 +148,7 @@ EditorLayersWidget::draw(DrawingContext& context) void EditorLayersWidget::update(float dt_sec) { - auto it = m_layer_icons.begin(); - while (it != m_layer_icons.end()) - { - auto layer_icon = (*it).get(); - if (!layer_icon->is_valid()) - { - it = m_layer_icons.erase(it); - continue; - } - ++it; - } + remove_invalid_layers(); TileMap* selected_tilemap = get_selected_tilemap(); if (selected_tilemap) @@ -460,6 +450,8 @@ EditorLayersWidget::add_layer(GameObject* layer, bool initial) void EditorLayersWidget::update_tip() { + remove_invalid_layers(); + if (m_hovered_layer == m_layer_icons.size()) m_object_tip->set_info(_("Add Layer")); else if (m_hovered_layer > m_layer_icons.size()) @@ -477,6 +469,22 @@ EditorLayersWidget::update_current_tip() update_tip(); } +void +EditorLayersWidget::remove_invalid_layers() +{ + auto it = m_layer_icons.begin(); + while (it != m_layer_icons.end()) + { + auto layer_icon = (*it).get(); + if (!layer_icon->is_valid()) + { + it = m_layer_icons.erase(it); + continue; + } + ++it; + } +} + TileMap* EditorLayersWidget::get_selected_tilemap() const { diff --git a/src/editor/layers_widget.hpp b/src/editor/layers_widget.hpp index e974fa91e26..3efc7355c42 100644 --- a/src/editor/layers_widget.hpp +++ b/src/editor/layers_widget.hpp @@ -78,7 +78,9 @@ class EditorLayersWidget final : public Widget private: Vector get_layer_coords(const int pos) const; int get_layer_pos(const Vector& coords) const; + void update_tip(); + void remove_invalid_layers(); private: Editor& m_editor; From 626ca5c5177734a486a4f175980ad7166aa51521 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:13:56 +0300 Subject: [PATCH 6/8] Rename `GameObjectChanges` to `GameObjectChangeSet`, fix compilation --- src/supertux/game_object_change.cpp | 14 ++++---- src/supertux/game_object_change.hpp | 19 ++++++----- src/supertux/game_object_manager.cpp | 50 ++++++++++++++-------------- src/supertux/game_object_manager.hpp | 6 ++-- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/supertux/game_object_change.cpp b/src/supertux/game_object_change.cpp index 8ca144b8c88..bb60d6f8495 100644 --- a/src/supertux/game_object_change.cpp +++ b/src/supertux/game_object_change.cpp @@ -55,15 +55,15 @@ GameObjectChange::save(Writer& writer) const } -GameObjectChanges::GameObjectChanges(const UID& uid_, std::vector objects_) : +GameObjectChangeSet::GameObjectChangeSet(const UID& uid_, std::vector objects_) : uid(uid_), - objects(std::move(objects_)) + changes(std::move(objects_)) { } -GameObjectChanges::GameObjectChanges(const ReaderMapping& reader) : +GameObjectChangeSet::GameObjectChangeSet(const ReaderMapping& reader) : uid(), - objects() + changes() { auto iter = reader.get_iter(); while (iter.next()) @@ -74,14 +74,14 @@ GameObjectChanges::GameObjectChanges(const ReaderMapping& reader) : continue; } - objects.push_back(GameObjectChange(iter.as_mapping())); + changes.push_back(GameObjectChange(iter.as_mapping())); } } void -GameObjectChanges::save(Writer& writer) const +GameObjectChangeSet::save(Writer& writer) const { - for (const auto& change : objects) + for (const auto& change : changes) { writer.start_list("object-change"); change.save(writer); diff --git a/src/supertux/game_object_change.hpp b/src/supertux/game_object_change.hpp index 39c6b91d1be..8fcbd0a193e 100644 --- a/src/supertux/game_object_change.hpp +++ b/src/supertux/game_object_change.hpp @@ -18,6 +18,7 @@ #define HEADER_SUPERTUX_SUPERTUX_GAME_OBJECT_CHANGE_HPP #include +#include #include "util/uid.hpp" @@ -28,11 +29,11 @@ class Writer; class GameObjectChange final { public: - enum class Action + enum Action { - CREATE, - DELETE, - MODIFY + ACTION_CREATE, + ACTION_DELETE, + ACTION_MODIFY }; public: @@ -51,18 +52,18 @@ class GameObjectChange final Action action; // The action which triggered a state change }; -/** Stores multiple GameObjectChanges. */ -class GameObjectChanges final +/** Stores multiple GameObjectChange-s. */ +class GameObjectChangeSet final { public: - GameObjectChanges(const UID& uid, std::vector objects); - GameObjectChanges(const ReaderMapping& reader); + GameObjectChangeSet(const UID& uid, std::vector objects); + GameObjectChangeSet(const ReaderMapping& reader); void save(Writer& writer) const; public: UID uid; - std::vector objects; + std::vector changes; }; #endif diff --git a/src/supertux/game_object_manager.cpp b/src/supertux/game_object_manager.cpp index d0d10ec46f3..eecf558bd68 100644 --- a/src/supertux/game_object_manager.cpp +++ b/src/supertux/game_object_manager.cpp @@ -385,13 +385,13 @@ GameObjectManager::apply_object_change(const GameObjectChange& change, bool trac GameObject* object = get_object_by_uid(change.uid); switch (change.action) { - case GameObjectChange::Action::CREATE: + case GameObjectChange::ACTION_CREATE: { create_object_from_change(change, track_undo); } break; - case GameObjectChange::Action::DELETE: + case GameObjectChange::ACTION_DELETE: { if (!object) throw std::runtime_error("Object '" + change.name + "' does not exist."); @@ -401,7 +401,7 @@ GameObjectManager::apply_object_change(const GameObjectChange& change, bool trac } break; - case GameObjectChange::Action::MODIFY: + case GameObjectChange::ACTION_MODIFY: { if (!object) throw std::runtime_error("Object '" + change.name + "' does not exist."); @@ -424,9 +424,9 @@ GameObjectManager::apply_object_change(const GameObjectChange& change, bool trac } void -GameObjectManager::apply_object_changes(const GameObjectChanges& changes, bool track_undo) +GameObjectManager::apply_object_changes(const GameObjectChangeSet& change_set, bool track_undo) { - for (const auto& change : changes.objects) + for (const auto& change : change_set.changes) { try { @@ -444,10 +444,10 @@ void GameObjectManager::undo() { if (m_undo_stack.empty()) return; - GameObjectChanges& changes = m_undo_stack.back(); + GameObjectChangeSet& change_set = m_undo_stack.back(); - auto it = changes.objects.begin(); - while (it != changes.objects.end()) + auto it = change_set.changes.begin(); + while (it != change_set.changes.end()) { try { @@ -457,14 +457,14 @@ GameObjectManager::undo() catch (const std::exception& err) { log_warning << "Cannot process object change: " << err.what() << std::endl; - it = changes.objects.erase(it); // Drop invalid changes + it = change_set.changes.erase(it); // Drop invalid changes } } - if (!changes.objects.empty()) + if (!change_set.changes.empty()) { // Changes have been reversed for redo - m_redo_stack.push_back(std::move(changes)); + m_redo_stack.push_back(std::move(change_set)); } m_undo_stack.pop_back(); } @@ -473,10 +473,10 @@ void GameObjectManager::redo() { if (m_redo_stack.empty()) return; - GameObjectChanges& changes = m_redo_stack.back(); + GameObjectChangeSet& change_set = m_redo_stack.back(); - auto it = changes.objects.begin(); - while (it != changes.objects.end()) + auto it = change_set.changes.begin(); + while (it != change_set.changes.end()) { try { @@ -486,14 +486,14 @@ GameObjectManager::redo() catch (const std::exception& err) { log_warning << "Cannot process object change: " << err.what() << std::endl; - it = changes.objects.erase(it); // Drop invalid changes + it = change_set.changes.erase(it); // Drop invalid changes } } - if (!changes.objects.empty()) + if (!change_set.changes.empty()) { // Changes have been reversed for undo - m_undo_stack.push_back(std::move(changes)); + m_undo_stack.push_back(std::move(change_set)); } m_redo_stack.pop_back(); } @@ -542,7 +542,7 @@ GameObjectManager::process_object_change(GameObjectChange& change) GameObject* object = get_object_by_uid(change.uid); switch (change.action) { - case GameObjectChange::Action::CREATE: /** Object was added, remove it. */ + case GameObjectChange::ACTION_CREATE: /** Object was added, remove it. */ { if (!object) throw std::runtime_error("Object '" + change.name + "' no longer exists."); @@ -552,20 +552,20 @@ GameObjectManager::process_object_change(GameObjectChange& change) // Prepare for redo change.data = object->save(); - change.action = GameObjectChange::Action::DELETE; + change.action = GameObjectChange::ACTION_DELETE; } break; - case GameObjectChange::Action::DELETE: /** Object was deleted, create it. */ + case GameObjectChange::ACTION_DELETE: /** Object was deleted, create it. */ { create_object_from_change(change, false); // Prepare for redo - change.action = GameObjectChange::Action::CREATE; + change.action = GameObjectChange::ACTION_CREATE; } break; - case GameObjectChange::Action::MODIFY: /** Object was modified, revert settings. */ + case GameObjectChange::ACTION_MODIFY: /** Object was modified, revert settings. */ { if (!object) throw std::runtime_error("Object '" + change.name + "' no longer exists."); @@ -605,7 +605,7 @@ GameObjectManager::save_object_change(const GameObject& object, const ObjectSett m_pending_change_stack.push_back({ object.get_class_name(), object.get_uid(), save_object_settings_state(settings, false), save_object_settings_state(settings, true), - GameObjectChange::Action::MODIFY }); + GameObjectChange::ACTION_MODIFY }); } void @@ -646,13 +646,13 @@ GameObjectManager::this_before_object_add(GameObject& object) } } - save_object_state(object, GameObjectChange::Action::CREATE); + save_object_state(object, GameObjectChange::ACTION_CREATE); } void GameObjectManager::this_before_object_remove(GameObject& object) { - save_object_state(object, GameObjectChange::Action::DELETE); + save_object_state(object, GameObjectChange::ACTION_DELETE); { // By name: const std::string& name = object.get_name(); diff --git a/src/supertux/game_object_manager.hpp b/src/supertux/game_object_manager.hpp index e2ac2c02fc0..e6da628f6aa 100644 --- a/src/supertux/game_object_manager.hpp +++ b/src/supertux/game_object_manager.hpp @@ -276,7 +276,7 @@ class GameObjectManager : public ExposableClass /** Apply saved object changes. */ void apply_object_change(const GameObjectChange& change, bool track_undo); - void apply_object_changes(const GameObjectChanges& changes, bool track_undo); + void apply_object_changes(const GameObjectChangeSet& changes, bool track_undo); /** Save object settings changes in the undo stack. Used to save an object's previous state before a change had occurred. */ @@ -346,8 +346,8 @@ class GameObjectManager : public ExposableClass UIDGenerator m_change_uid_generator; bool m_undo_tracking; int m_undo_stack_size; - std::vector m_undo_stack; - std::vector m_redo_stack; + std::vector m_undo_stack; + std::vector m_redo_stack; std::vector m_pending_change_stack; // Before a flush, any changes go here UID m_last_saved_change; From c941522cbecd395114d62e66bc9335193b385cbd Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:50:50 +0300 Subject: [PATCH 7/8] `ObjectSettings`: Fix comment [ci skip] --- src/editor/object_settings.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/object_settings.hpp b/src/editor/object_settings.hpp index c56828745d6..df523d17113 100644 --- a/src/editor/object_settings.hpp +++ b/src/editor/object_settings.hpp @@ -173,7 +173,7 @@ class ObjectSettings final /** Remove an option from the list, this is a hack */ void remove(const std::string& key); - /** Parse option properties from a previous state. */ + /** Parse option properties. */ void parse(const ReaderMapping& reader); /** Save the current states of all options. */ From 4fbe7d9112c9c588fca06553dbe64e5faf85d5c2 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:19:39 +0300 Subject: [PATCH 8/8] `objects` -> `changes` [ci skip] --- src/supertux/game_object_change.cpp | 4 ++-- src/supertux/game_object_change.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/supertux/game_object_change.cpp b/src/supertux/game_object_change.cpp index bb60d6f8495..db0a3b184dc 100644 --- a/src/supertux/game_object_change.cpp +++ b/src/supertux/game_object_change.cpp @@ -55,9 +55,9 @@ GameObjectChange::save(Writer& writer) const } -GameObjectChangeSet::GameObjectChangeSet(const UID& uid_, std::vector objects_) : +GameObjectChangeSet::GameObjectChangeSet(const UID& uid_, std::vector changes_) : uid(uid_), - changes(std::move(objects_)) + changes(std::move(changes_)) { } diff --git a/src/supertux/game_object_change.hpp b/src/supertux/game_object_change.hpp index 8fcbd0a193e..96624adf460 100644 --- a/src/supertux/game_object_change.hpp +++ b/src/supertux/game_object_change.hpp @@ -56,7 +56,7 @@ class GameObjectChange final class GameObjectChangeSet final { public: - GameObjectChangeSet(const UID& uid, std::vector objects); + GameObjectChangeSet(const UID& uid, std::vector changes); GameObjectChangeSet(const ReaderMapping& reader); void save(Writer& writer) const;