Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compress TileMap tiles in level file #3124

Merged
merged 11 commits into from
Dec 5, 2024
6 changes: 2 additions & 4 deletions src/editor/object_option.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,11 +711,9 @@ TilesObjectOption::parse(const ReaderMapping& reader)
}

void
TilesObjectOption::save(Writer& write) const
TilesObjectOption::save(Writer& writer) const
{
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());
m_value_pointer->write_tiles(writer);
}

std::string
Expand Down
10 changes: 9 additions & 1 deletion src/object/tilemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ TileMap::parse_tiles(const ReaderMapping& reader)
}
else
{
reader.get("tiles", m_tiles);
reader.get_compressed("tiles", m_tiles);
if (m_tiles.empty())
throw std::runtime_error("No tiles in tilemap.");

Expand Down Expand Up @@ -206,6 +206,14 @@ TileMap::parse_tiles(const ReaderMapping& reader)
m_new_offset_y = 0;
}

void
TileMap::write_tiles(Writer& writer) const
{
writer.write("width", m_width);
writer.write("height", m_height);
writer.write_compressed("tiles", m_tiles, m_width);
}

void
TileMap::finish_construction()
{
Expand Down
5 changes: 3 additions & 2 deletions src/object/tilemap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ class TileMap final : public LayerObject,
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"; }
Expand All @@ -84,6 +82,9 @@ class TileMap final : public LayerObject,

virtual void on_flip(float height) override;

void parse_tiles(const ReaderMapping& reader);
void write_tiles(Writer& writer) const;

void set(int width, int height, const std::vector<unsigned int>& vec,
int z_pos, bool solid);

Expand Down
46 changes: 46 additions & 0 deletions src/util/reader_mapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,52 @@ ReaderMapping::get(const char* key, std::vector<unsigned int>& value,

#undef GET_VALUES_MACRO

bool
ReaderMapping::get_compressed(const char* key, std::vector<unsigned int>& value,
const std::optional<std::vector<unsigned int>>& default_value) const
{
const auto sx = get_item(key);
if (!sx)
{
if (default_value)
value = *default_value;
return false;
}

assert_is_array(m_doc, *sx);
value.clear();
const auto& item = sx->as_array();
int multiplier = 0;
Vankata453 marked this conversation as resolved.
Show resolved Hide resolved
for (size_t i = 1; i < item.size(); ++i)
{
assert_is_integer(m_doc, item[i]);

const int val = item[i].as_int();
if (multiplier)
{
if (val < 0)
{
raise_exception(m_doc, item[i], "expected positive integer after multiplier");
}
value.insert(value.end(), multiplier, val);
multiplier = 0;
}
else if (val < 0)
{
multiplier = -val;
}
else
{
value.push_back(val);
Vankata453 marked this conversation as resolved.
Show resolved Hide resolved
}
}
if (multiplier)
{
raise_exception(m_doc, item.back(), "expected positive integer after multiplier");
}
return true;
}

bool
ReaderMapping::get(const char* key, std::optional<ReaderMapping>& value) const
{
Expand Down
4 changes: 4 additions & 0 deletions src/util/reader_mapping.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class ReaderMapping final
bool get(const char* key, std::vector<std::string>& value, const std::optional<std::vector<std::string>>& default_value = std::nullopt) const;
bool get(const char* key, std::vector<unsigned int>& value, const std::optional<std::vector<unsigned int>>& default_value = std::nullopt) const;

// Reads vector by using any negative integer values as a multiplier for the next value.
bool get_compressed(const char* key, std::vector<unsigned int>& value,
const std::optional<std::vector<unsigned int>>& default_value = std::nullopt) const;

bool get(const char* key, std::optional<ReaderMapping>&) const;
bool get(const char* key, std::optional<ReaderCollection>&) const;

Expand Down
53 changes: 53 additions & 0 deletions src/util/writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,59 @@ Writer::write(const std::string& name, const sexp::Value& value)
*out << ")\n";
}

void
Writer::write_compressed(const std::string& name, const std::vector<unsigned int>& value, int width)
{
indent();
*out << '(' << name;
if (width > 0)
{
*out << "\n";
indent();
}

int count = 0;
int multiplier = 0;
Vankata453 marked this conversation as resolved.
Show resolved Hide resolved
unsigned int multiplied_value = 0;
for (const auto& i : value)
{
const bool width_limit = (width > 0 && count >= width);

++count;
if (multiplier > 0 && i == multiplied_value)
{
++multiplier;
}
else
{
if (multiplier > 1)
*out << -multiplier << " " << multiplied_value << (width_limit ? "" : " ");
else if (multiplier == 1)
*out << multiplied_value << (width_limit ? "" : " ");

multiplier = 1;
multiplied_value = i;
}

if (width > 0 && count >= width)
{
if (multiplier > 1)
*out << -multiplier << " " << multiplied_value;
else if (multiplier == 1)
*out << multiplied_value;

count = 0;
multiplier = 0;
multiplied_value = 0;

*out << "\n";
Vankata453 marked this conversation as resolved.
Show resolved Hide resolved
indent();
}
}

*out << ")\n";
}

void
Writer::write_escaped_string(const std::string& str)
{
Expand Down
3 changes: 3 additions & 0 deletions src/util/writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class Writer final
void write(const std::string& name, const sexp::Value& value);
// add more write-functions when needed...

// Writes vector by using negative integer values as multipliers for repeating values.
void write_compressed(const std::string& name, const std::vector<unsigned int>& value, int width = 0);

void end_list(const std::string& listname);

private:
Expand Down
31 changes: 28 additions & 3 deletions tests/reader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ TEST(ReaderTest, get)
" (mystring \"Hello World\")\n"
" (mystringtrans (_ \"Hello World\"))\n"
" (myboolarray #t #f #t #f)\n"
" (myintarray 5 4 3 2 1 0)\n"
" (myintarray 5 5 4 4 3 2 1 0)\n"
" (myfloatarray 6.5 5.25 4.125 3.0625 2.0 1.0 0.5 0.25 0.125)\n"
" (mystringarray \"One\" \"Two\" \"Three\")\n"
" (mymapping (a 1) (b 2))\n"
Expand Down Expand Up @@ -79,7 +79,7 @@ TEST(ReaderTest, get)
}

{
std::vector<int> expected{ 5, 4, 3, 2, 1, 0 };
std::vector<int> expected{ 5, 5, 4, 4, 3, 2, 1, 0 };
std::vector<int> result;
mapping.get("myintarray", result);
ASSERT_EQ(expected, result);
Expand Down Expand Up @@ -139,6 +139,26 @@ TEST(ReaderTest, get)
}
}

TEST(ReaderTest, get_compressed)
{
std::istringstream in(
"(supertux-test\n"
" (mycompressedintarray -6 0 45 -4 1 -5 3 -2 0)\n"
")\n");

auto doc = ReaderDocument::from_stream(in);
auto root = doc.get_root();
ASSERT_EQ("supertux-test", root.get_name());
auto mapping = root.get_mapping();

{
std::vector<unsigned int> expected{ 0, 0, 0, 0, 0, 0, 45, 1, 1, 1, 1, 3, 3, 3, 3, 3, 0, 0, };
std::vector<unsigned int> result;
mapping.get_compressed("mycompressedintarray", result);
ASSERT_EQ(expected, result);
}
}

TEST(ReaderTest, syntax_error)
{
std::istringstream in(
Expand All @@ -148,6 +168,8 @@ TEST(ReaderTest, syntax_error)
" (myfloat 1.125 err)\n\r"
" (mystring \"Hello World\" err)\n"
" (mystringtrans (_ \"Hello World\" err))\n"
" (mycompressedintarray1 -5 0 43 -5 -12 3 -1 5)\n"
Vankata453 marked this conversation as resolved.
Show resolved Hide resolved
" (mycompressedintarray2 -5 0 43 -5 12 -3 1 -5)\n"
" (mymapping err (a 1) (b 2))\n"
")\n");

Expand All @@ -159,11 +181,14 @@ TEST(ReaderTest, syntax_error)
bool mybool;
int myint;
float myfloat;
std::optional<ReaderMapping> mymapping;
std::vector<unsigned int> mycompressedintarray;
ASSERT_THROW({mapping.get("mybool", mybool);}, std::runtime_error);
ASSERT_THROW({mapping.get("myint", myint);}, std::runtime_error);
ASSERT_THROW({mapping.get("myfloat", myfloat);}, std::runtime_error);
ASSERT_THROW({mapping.get_compressed("mycompressedintarray1", mycompressedintarray);}, std::runtime_error);
ASSERT_THROW({mapping.get_compressed("mycompressedintarray2", mycompressedintarray);}, std::runtime_error);

std::optional<ReaderMapping> mymapping;
mapping.get("mymapping", mymapping);
ASSERT_THROW({mymapping->get("a", myint);}, std::runtime_error);
ASSERT_THROW({mymapping->get("b", myint);}, std::runtime_error);
Expand Down