diff --git a/src/Common/Compression.cpp b/src/Common/Compression.cpp new file mode 100644 index 0000000..f09c6d1 --- /dev/null +++ b/src/Common/Compression.cpp @@ -0,0 +1,79 @@ +#include "Compression.h" + +#include +#include + +#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(result.data()), reinterpret_cast(&uncompressedSize), + reinterpret_cast(source), static_cast(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(result.data()), &compressedSize, reinterpret_cast(source), static_cast(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); +} diff --git a/src/Common/Compression.h b/src/Common/Compression.h new file mode 100644 index 0000000..96eff3e --- /dev/null +++ b/src/Common/Compression.h @@ -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); diff --git a/src/Common/DataBuffer.cpp b/src/Common/DataBuffer.cpp new file mode 100644 index 0000000..13923fd --- /dev/null +++ b/src/Common/DataBuffer.cpp @@ -0,0 +1,98 @@ +#include "DataBuffer.h" + +#include +#include +#include +#include + +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(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(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(m_Buffer.data()), static_cast(m_Buffer.size())); + file.flush(); + } catch (std::exception& e) { + std::cerr << "Failed to write file : " << e.what() << std::endl; + return false; + } + return true; +} diff --git a/src/Common/DataBuffer.h b/src/Common/DataBuffer.h new file mode 100644 index 0000000..27b48ea --- /dev/null +++ b/src/Common/DataBuffer.h @@ -0,0 +1,284 @@ +#pragma once + +/** + * \file DataBuffer.h + * \brief File containing the blitz::DataBuffer class + */ + +#include +#include +#include +#include +#include +#include + +/** + * \class DataBuffer + * \brief Class used to manipulate memory + */ +class DataBuffer { + private: + typedef std::vector 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 + 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 + 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 + DataBuffer& operator>>(T& data) { + assert(m_ReadOffset + sizeof(T) <= GetSize()); + data = *(reinterpret_cast(&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(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(m_Buffer.data()) + m_ReadOffset) + 1; // including null character + str.resize(stringSize); + std::copy(m_Buffer.begin() + static_cast(m_ReadOffset), + m_Buffer.begin() + static_cast(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(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(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(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); + diff --git a/src/Pieces/PiecesFiles.cpp b/src/Pieces/PiecesFiles.cpp index 7034928..bebff11 100644 --- a/src/Pieces/PiecesFiles.cpp +++ b/src/Pieces/PiecesFiles.cpp @@ -10,6 +10,8 @@ #include #include +#include "../Common/Compression.h" + PiecesFiles::PiecesFiles() { } @@ -19,10 +21,6 @@ bool PiecesFiles::savePieces(int polyominoSize) const { if (!this->getFilePath(polyominoSize, filePath)) { return false; } - std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary); - if (!piecesFile.good()) { - return false; - } Generator generator; std::vector polyominoes = generator.generatePolyominoes(polyominoSize); @@ -35,10 +33,9 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector& polyomin if (!this->getFilePath(polyominoSize, filePath)) { return false; } - std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary); - if (!piecesFile.good()) { - return false; - } + + constexpr std::size_t INITIAL_CAPACITY = 2048; + DataBuffer buffer(INITIAL_CAPACITY); // sorting the polyominoes is done after setting spawn position to ensure the order is always the same for (Polyomino& nMino : polyominoes) { @@ -50,18 +47,20 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector& polyomin // write the characteristics of the piece bool isConvex = polyomino.isConvex(); bool hasHole = (isConvex) ? false : polyomino.hasHole(); - char infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength(); - piecesFile.write(&infoByte, 1); + std::uint8_t infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength(); + buffer << infoByte; // write the positions of the piece - char positionByte; + std::uint8_t positionByte; for (const Position position : polyomino.getPositions()) { 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& pieces, std::vector& convexPieces, std::vector& holelessPieces, std::vector& otherPieces) const { @@ -69,6 +68,13 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector& pieces, std: if (!this->getFilePath(polyominoSize, filePath)) { return false; } + + DataBuffer compressed; + if(!compressed.ReadFile(filePath)) + return false; + + DataBuffer buffer = Decompress(compressed); + std::ifstream piecesFile(filePath, std::ios::binary); if (!piecesFile.good()) { return false; @@ -91,10 +97,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector& pieces, std: char xMask = 0b1111'0000; char yMask = 0b0000'1111; - char infoByte; + std::uint8_t infoByte; int i = 0; - while (piecesFile.get(infoByte)) { - if (piecesFile.eof()) break; + while (!buffer.IsFinished()) { + // if (piecesFile.eof()) break; + buffer >> infoByte; // read piece infos bool isConvex = (infoByte & convexMask) >> 7; @@ -103,9 +110,9 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector& pieces, std: // read positions std::set piecePositions; - char positionByte; + std::uint8_t positionByte; for (int i = 0; i < polyominoSize; i++) { - piecesFile.get(positionByte); + buffer >> positionByte; int x = ((unsigned char) positionByte & xMask) >> 4; int y = positionByte & yMask; piecePositions.insert(Position{x, y}); diff --git a/xmake.lua b/xmake.lua index a1e1e22..b306659 100644 --- a/xmake.lua +++ b/xmake.lua @@ -2,7 +2,7 @@ add_rules("mode.debug", "mode.release") includes("xmake/bin2c.lua") -add_requires("sfml 3.0.0") +add_requires("sfml 3.0.0", "zlib") set_languages("c++20") @@ -10,8 +10,8 @@ set_rundir(".") target("core") set_kind("$(kind)") - add_files("src/Pieces/*.cpp") - add_files("src/Core/*.cpp") + add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp") + add_packages("zlib") target("text") set_default(false)