Skip to content

Commit

Permalink
TileMap: Proper tile change tracking
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
Vankata453 committed Oct 21, 2024
1 parent 7c4a0f7 commit 3d5d306
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 13 deletions.
84 changes: 83 additions & 1 deletion src/editor/object_option.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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()
{
}

Expand Down Expand Up @@ -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<int> 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<uint32_t>(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<uint32_t> tile_changes; // Array of pairs (index, old/new tile ID).
for (uint32_t i = 0; i < static_cast<uint32_t>(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)
Expand Down
20 changes: 20 additions & 0 deletions src/editor/object_option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -382,6 +383,25 @@ class TilesObjectOption final : public ObjectOption<TileMap>
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<uint32_t> tiles;
};
TilesState m_last_tiles_state;

private:
TilesObjectOption(const TilesObjectOption&) = delete;
TilesObjectOption& operator=(const TilesObjectOption&) = delete;
Expand Down
23 changes: 20 additions & 3 deletions src/editor/object_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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;
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/editor/object_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 15 additions & 6 deletions src/object/tilemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,21 +164,24 @@ 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;
m_tiles.clear();
resize(static_cast<int>(Sector::get().get_width() / 32.0f),
static_cast<int>(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<int>(m_tiles.size()) != m_width * m_height)
throw std::runtime_error("wrong number of tiles in tilemap.");
}
}

bool empty = true;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -1002,7 +1011,7 @@ TileMap::register_class(ssq::VM& vm)

cls.addFunc("get_tile_id", &TileMap::get_tile_id);
cls.addFunc<uint32_t, TileMap, float, float>("get_tile_id_at", &TileMap::get_tile_id_at);
cls.addFunc("change", &TileMap::change);
cls.addFunc<void, TileMap, int, int, uint32_t>("change", &TileMap::change);
cls.addFunc<void, TileMap, float, float, uint32_t>("change_at", &TileMap::change_at);
cls.addFunc("change_all", &TileMap::change_all);
cls.addFunc("fade", &TileMap::fade);
Expand Down
1 change: 1 addition & 0 deletions src/object/tilemap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"".
Expand Down
2 changes: 1 addition & 1 deletion src/supertux/game_object_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3d5d306

Please sign in to comment.