add compression
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s

This commit is contained in:
2025-06-12 21:28:46 +02:00
parent dd8314f76c
commit 3e40ff7252
6 changed files with 519 additions and 21 deletions

View File

@@ -0,0 +1,79 @@
#include "Compression.h"
#include <cassert>
#include <zlib.h>
#define COMPRESSION_THRESHOLD 64
static DataBuffer Inflate(const std::uint8_t* source, std::size_t size, std::size_t uncompressedSize) {
DataBuffer result;
result.Resize(uncompressedSize);
uncompress(reinterpret_cast<Bytef*>(result.data()), reinterpret_cast<uLongf*>(&uncompressedSize),
reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
assert(result.GetSize() == uncompressedSize);
return result;
}
static DataBuffer Deflate(const std::uint8_t* source, std::size_t size) {
DataBuffer result;
uLongf compressedSize = size;
result.Resize(size); // Resize for the compressed data to fit into
compress(
reinterpret_cast<Bytef*>(result.data()), &compressedSize, reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
result.Resize(compressedSize); // Resize to cut useless data
return result;
}
DataBuffer Compress(const DataBuffer& buffer) {
DataBuffer packet;
if (buffer.GetSize() < COMPRESSION_THRESHOLD) {
// Don't compress since it's a small packet
std::uint64_t compressedDataLength = 0;
std::uint64_t packetLength = compressedDataLength + buffer.GetSize();
packet << packetLength;
packet << compressedDataLength;
packet << buffer;
return packet;
}
DataBuffer compressedData = Deflate(buffer.data(), buffer.GetSize());
std::uint64_t uncompressedDataLength = buffer.GetSize();
std::uint64_t packetLength = uncompressedDataLength + compressedData.GetSize();
packet << packetLength;
packet << uncompressedDataLength;
packet.WriteSome(compressedData.data(), compressedData.GetSize());
return packet;
}
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength) {
std::uint64_t uncompressedLength;
buffer >> uncompressedLength;
std::uint64_t compressedLength = packetLength - uncompressedLength;
if (uncompressedLength == 0) {
// Data already uncompressed. Nothing to do
DataBuffer ret;
buffer.ReadSome(ret, compressedLength);
return ret;
}
assert(buffer.GetReadOffset() + compressedLength <= buffer.GetSize());
return Inflate(buffer.data() + buffer.GetReadOffset(), compressedLength, uncompressedLength);
}
DataBuffer Decompress(DataBuffer& buffer) {
std::uint64_t packetLength;
buffer >> packetLength;
return Decompress(buffer, packetLength);
}

30
src/Common/Compression.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
/**
* \file Compression.h
* \brief File containing compress utilities
*/
#include "DataBuffer.h"
/**
* \brief Compress some data
* \param buffer the data to compress
* \return the compressed data
*/
DataBuffer Compress(const DataBuffer& buffer);
/**
* \brief Reads the packet lenght and uncompress it
* \param buffer the data to uncompress
* \return the uncompressed data
*/
DataBuffer Decompress(DataBuffer& buffer);
/**
* \brief Uncompress some data
* \param buffer the data to uncompress
* \param packetLength lenght of data
* \return the uncompressed data
*/
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength);

98
src/Common/DataBuffer.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "DataBuffer.h"
#include <fstream>
#include <iomanip>
#include <sstream>
#include <iostream>
DataBuffer::DataBuffer(std::size_t initalCapacity) : m_ReadOffset(0) {
m_Buffer.reserve(initalCapacity);
}
DataBuffer::DataBuffer() : m_ReadOffset(0) {}
DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {}
DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}
DataBuffer::DataBuffer(const std::string& str) : m_Buffer(str.begin(), str.end()), m_ReadOffset(0) {}
DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) : m_ReadOffset(0) {
m_Buffer.reserve(other.GetSize() - static_cast<std::size_t>(offset));
std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer));
}
DataBuffer& DataBuffer::operator=(const DataBuffer& other) {
m_Buffer = other.m_Buffer;
m_ReadOffset = other.m_ReadOffset;
return *this;
}
DataBuffer& DataBuffer::operator=(DataBuffer&& other) {
m_Buffer = std::move(other.m_Buffer);
m_ReadOffset = std::move(other.m_ReadOffset);
return *this;
}
void DataBuffer::SetReadOffset(std::size_t pos) {
assert(pos <= GetSize());
m_ReadOffset = pos;
}
std::size_t DataBuffer::GetSize() const {
return m_Buffer.size();
}
std::size_t DataBuffer::GetRemaining() const {
return m_Buffer.size() - m_ReadOffset;
}
DataBuffer::iterator DataBuffer::begin() {
return m_Buffer.begin();
}
DataBuffer::iterator DataBuffer::end() {
return m_Buffer.end();
}
DataBuffer::const_iterator DataBuffer::begin() const {
return m_Buffer.begin();
}
DataBuffer::const_iterator DataBuffer::end() const {
return m_Buffer.end();
}
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
for (unsigned char u : buffer)
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(u) << " ";
os << std::dec;
return os;
}
bool DataBuffer::ReadFile(const std::string& fileName) {
try {
std::ifstream file(fileName, std::istream::binary);
std::ostringstream ss;
ss << file.rdbuf();
const std::string& s = ss.str();
m_Buffer = DataBuffer::Data(s.begin(), s.end());
m_ReadOffset = 0;
} catch (std::exception& e) {
std::cerr << "Failed to read file : " << e.what() << std::endl;
return false;
}
return m_Buffer.size() > 0;
}
bool DataBuffer::WriteFile(const std::string& fileName) const {
try {
std::ofstream file(fileName, std::ostream::binary);
file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
file.flush();
} catch (std::exception& e) {
std::cerr << "Failed to write file : " << e.what() << std::endl;
return false;
}
return true;
}

284
src/Common/DataBuffer.h Normal file
View File

@@ -0,0 +1,284 @@
#pragma once
/**
* \file DataBuffer.h
* \brief File containing the blitz::DataBuffer class
*/
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
/**
* \class DataBuffer
* \brief Class used to manipulate memory
*/
class DataBuffer {
private:
typedef std::vector<std::uint8_t> Data;
Data m_Buffer;
std::size_t m_ReadOffset;
public:
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
typedef Data::reference reference;
typedef Data::const_reference const_reference;
typedef Data::difference_type difference_type;
DataBuffer();
DataBuffer(std::size_t initalCapacity);
DataBuffer(const DataBuffer& other);
DataBuffer(const DataBuffer& other, difference_type offset);
DataBuffer(DataBuffer&& other);
DataBuffer(const std::string& str);
DataBuffer& operator=(const DataBuffer& other);
DataBuffer& operator=(DataBuffer&& other);
/**
* \brief Append data to the buffer
*/
template <typename T>
void Append(const T& data) {
std::size_t size = sizeof(data);
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + size);
std::memcpy(&m_Buffer[end_pos], &data, size);
}
/**
* \brief Append data to the buffer
*/
template <typename T>
DataBuffer& operator<<(const T& data) {
Append(data);
return *this;
}
/**
* \brief Append a string to the buffer
* \warning Don't use it for binary data !
* \param str The string to append
*/
DataBuffer& operator<<(const std::string& str) {
std::size_t strlen = str.length() + 1; // including null character
Resize(GetSize() + strlen);
std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen);
return *this;
}
/**
* \brief Append data to the buffer from another const buffer
* \param data The buffer to append
*/
DataBuffer& operator<<(const DataBuffer& data) {
m_Buffer.insert(m_Buffer.end(), data.begin(), data.end());
return *this;
}
/**
* \brief Read some data from the buffer and assign to desired variable
*/
template <typename T>
DataBuffer& operator>>(T& data) {
assert(m_ReadOffset + sizeof(T) <= GetSize());
data = *(reinterpret_cast<T*>(&m_Buffer[m_ReadOffset]));
m_ReadOffset += sizeof(T);
return *this;
}
/**
* \brief Read some data from the buffer and assign to the new buffer
* \param data The buffer to assign
*/
DataBuffer& operator>>(DataBuffer& data) {
data.Resize(GetSize() - m_ReadOffset);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), m_Buffer.end(), data.begin());
m_ReadOffset = m_Buffer.size();
return *this;
}
/**
* \brief Read a string from the buffer
* \param str The string to assign in the new buffer
* \warning Don't use it for binary data !
*/
DataBuffer& operator>>(std::string& str) {
std::size_t stringSize =
strlen(reinterpret_cast<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character
str.resize(stringSize);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset),
m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset + stringSize), str.begin());
m_ReadOffset += stringSize;
return *this;
}
/**
* \brief Write some data to the buffer
* \param buffer The buffer to write
* \param amount The amount of data to write
*/
void WriteSome(const char* buffer, std::size_t amount) {
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + amount);
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
}
/**
* \brief Write some data to the buffer
* \param buffer The buffer to write
* \param amount The amount of data to write
*/
void WriteSome(const std::uint8_t* buffer, std::size_t amount) {
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + amount);
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(char* buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
m_ReadOffset += amount;
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(std::uint8_t* buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
m_ReadOffset += amount;
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(DataBuffer& buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
buffer.Resize(amount);
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer.begin());
m_ReadOffset += amount;
}
/**
* \brief Resize the buffer
* \param size The new size of the buffer
*/
void Resize(std::size_t size) {
m_Buffer.resize(size);
}
/**
* \brief Reserve some space in the buffer
* \param amount The amount of space to reserve
*/
void Reserve(std::size_t amount) {
m_Buffer.reserve(amount);
}
/**
* \brief Clear the buffer
*/
void Clear() {
m_Buffer.clear();
m_ReadOffset = 0;
}
/**
* \brief When the buffer has been read entirely
*/
bool IsFinished() const {
return m_ReadOffset >= m_Buffer.size();
}
/**
* \brief Get the buffer data
*/
std::uint8_t* data() {
return m_Buffer.data();
}
/**
* \brief Get the buffer data
*/
const std::uint8_t* data() const {
return m_Buffer.data();
}
/**
* \brief Get the read offset
*/
std::size_t GetReadOffset() const {
return m_ReadOffset;
}
/**
* \brief Set the read offset
* \param pos The new read offset
*/
void SetReadOffset(std::size_t pos);
/**
* \brief Get the size of the buffer
*/
std::size_t GetSize() const;
/**
* \brief Get the remaining size of the buffer
*/
std::size_t GetRemaining() const;
/**
* \brief Read a file into the buffer
* \param fileName The name of the file to read
*/
bool ReadFile(const std::string& fileName);
/**
* \brief Write a file into the buffer
* \param fileName The name of the file to write to
*/
bool WriteFile(const std::string& fileName) const;
/**
* \brief Allocate the buffer on the heap
* \warning Don't forget to free the data !
*/
std::uint8_t* HeapAllocatedData() const {
std::uint8_t* newBuffer = new std::uint8_t[GetSize()];
std::memcpy(newBuffer, data(), GetSize());
return newBuffer;
}
/**
* \brief Operator == to compare two DataBuffer
*/
bool operator==(const DataBuffer& other) const {
return m_Buffer == other.m_Buffer;
}
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
};
/**
* \brief Operator << to write a DataBuffer to an ostream
*/
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer);

View File

@@ -10,6 +10,8 @@
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
#include "../Common/Compression.h"
PiecesFiles::PiecesFiles() { PiecesFiles::PiecesFiles() {
} }
@@ -19,10 +21,6 @@ bool PiecesFiles::savePieces(int polyominoSize) const {
if (!this->getFilePath(polyominoSize, filePath)) { if (!this->getFilePath(polyominoSize, filePath)) {
return false; return false;
} }
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
if (!piecesFile.good()) {
return false;
}
Generator generator; Generator generator;
std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize); std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize);
@@ -35,10 +33,9 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyomin
if (!this->getFilePath(polyominoSize, filePath)) { if (!this->getFilePath(polyominoSize, filePath)) {
return false; return false;
} }
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
if (!piecesFile.good()) { constexpr std::size_t INITIAL_CAPACITY = 2048;
return false; DataBuffer buffer(INITIAL_CAPACITY);
}
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same // sorting the polyominoes is done after setting spawn position to ensure the order is always the same
for (Polyomino& nMino : polyominoes) { for (Polyomino& nMino : polyominoes) {
@@ -50,18 +47,20 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyomin
// write the characteristics of the piece // write the characteristics of the piece
bool isConvex = polyomino.isConvex(); bool isConvex = polyomino.isConvex();
bool hasHole = (isConvex) ? false : polyomino.hasHole(); bool hasHole = (isConvex) ? false : polyomino.hasHole();
char infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength(); std::uint8_t infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength();
piecesFile.write(&infoByte, 1); buffer << infoByte;
// write the positions of the piece // write the positions of the piece
char positionByte; std::uint8_t positionByte;
for (const Position position : polyomino.getPositions()) { for (const Position position : polyomino.getPositions()) {
positionByte = (position.x << 4) + position.y; positionByte = (position.x << 4) + position.y;
piecesFile.write(&positionByte, 1); buffer << positionByte;
} }
} }
return true; DataBuffer compressed = Compress(buffer);
return compressed.WriteFile(filePath);
} }
bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const { bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const {
@@ -69,6 +68,13 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
if (!this->getFilePath(polyominoSize, filePath)) { if (!this->getFilePath(polyominoSize, filePath)) {
return false; return false;
} }
DataBuffer compressed;
if(!compressed.ReadFile(filePath))
return false;
DataBuffer buffer = Decompress(compressed);
std::ifstream piecesFile(filePath, std::ios::binary); std::ifstream piecesFile(filePath, std::ios::binary);
if (!piecesFile.good()) { if (!piecesFile.good()) {
return false; return false;
@@ -91,10 +97,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
char xMask = 0b1111'0000; char xMask = 0b1111'0000;
char yMask = 0b0000'1111; char yMask = 0b0000'1111;
char infoByte; std::uint8_t infoByte;
int i = 0; int i = 0;
while (piecesFile.get(infoByte)) { while (!buffer.IsFinished()) {
if (piecesFile.eof()) break; // if (piecesFile.eof()) break;
buffer >> infoByte;
// read piece infos // read piece infos
bool isConvex = (infoByte & convexMask) >> 7; bool isConvex = (infoByte & convexMask) >> 7;
@@ -103,9 +110,9 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
// read positions // read positions
std::set<Position> piecePositions; std::set<Position> piecePositions;
char positionByte; std::uint8_t positionByte;
for (int i = 0; i < polyominoSize; i++) { for (int i = 0; i < polyominoSize; i++) {
piecesFile.get(positionByte); buffer >> positionByte;
int x = ((unsigned char) positionByte & xMask) >> 4; int x = ((unsigned char) positionByte & xMask) >> 4;
int y = positionByte & yMask; int y = positionByte & yMask;
piecePositions.insert(Position{x, y}); piecePositions.insert(Position{x, y});

View File

@@ -2,7 +2,7 @@ add_rules("mode.debug", "mode.release")
includes("xmake/bin2c.lua") includes("xmake/bin2c.lua")
add_requires("sfml 3.0.0") add_requires("sfml 3.0.0", "zlib")
set_languages("c++20") set_languages("c++20")
@@ -10,8 +10,8 @@ set_rundir(".")
target("core") target("core")
set_kind("$(kind)") set_kind("$(kind)")
add_files("src/Pieces/*.cpp") add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp")
add_files("src/Core/*.cpp") add_packages("zlib")
target("text") target("text")
set_default(false) set_default(false)