diff --git a/core/include/vf/ChunkSync.h b/core/include/vf/ChunkSync.h new file mode 100644 index 0000000..7036558 --- /dev/null +++ b/core/include/vf/ChunkSync.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include "Chunk.h" +#include "Math.h" +#include "Protocol.h" +#include "World.h" + +namespace vf { + +// Server side of chunk replication: records which block slots in each chunk +// changed since the last flush and turns them into wire packets. +class ServerChunkSync { +public: + void markDirty(const Vec3i& chunkCoord, int x, int y, int z); + void markDirtyIndex(const Vec3i& chunkCoord, int localIndex); + + std::size_t dirtyCount(const Vec3i& chunkCoord) const; + bool anyDirty() const; + void clear(const Vec3i& chunkCoord); + void clearAll() { dirty.clear(); } + + // A full chunk snapshot for the initial send; false when the chunk is absent. + bool buildFull(const World& world, const Vec3i& chunkCoord, ChunkData& out) const; + + // An incremental update from the dirty set; false when nothing is dirty. + bool buildDelta(const World& world, const Vec3i& chunkCoord, MultiBlockChange& out) const; + + // Whether a full resend is cheaper than a per-block delta at this dirty size. + bool shouldResendFull(const Vec3i& chunkCoord, std::size_t threshold) const; + +private: + std::unordered_map, Vec3iHash> dirty; +}; + +// Client side: applies received packets into a local world mirror. +class ClientChunkSync { +public: + void applyFull(const ChunkData& packet, World& world); + void applyDelta(const MultiBlockChange& packet, World& world); + void applyBlockChange(const BlockChange& packet, World& world); +}; + +} // namespace vf diff --git a/core/src/ChunkSync.cpp b/core/src/ChunkSync.cpp new file mode 100644 index 0000000..2036012 --- /dev/null +++ b/core/src/ChunkSync.cpp @@ -0,0 +1,71 @@ +#include "vf/ChunkSync.h" + +namespace vf { + +void ServerChunkSync::markDirty(const Vec3i& chunkCoord, int x, int y, int z) { + dirty[chunkCoord].insert(localIndex(x, y, z)); +} + +void ServerChunkSync::markDirtyIndex(const Vec3i& chunkCoord, int localIndex) { + dirty[chunkCoord].insert(localIndex); +} + +std::size_t ServerChunkSync::dirtyCount(const Vec3i& chunkCoord) const { + auto it = dirty.find(chunkCoord); + return it == dirty.end() ? 0 : it->second.size(); +} + +bool ServerChunkSync::anyDirty() const { + for (const auto& entry : dirty) + if (!entry.second.empty()) return true; + return false; +} + +void ServerChunkSync::clear(const Vec3i& chunkCoord) { dirty.erase(chunkCoord); } + +bool ServerChunkSync::buildFull(const World& world, const Vec3i& chunkCoord, ChunkData& out) const { + const Chunk* chunk = world.chunkAt(chunkCoord); + if (!chunk) return false; + out.setFromChunk(chunkCoord.x, chunkCoord.y, chunkCoord.z, *chunk); + return true; +} + +bool ServerChunkSync::buildDelta(const World& world, const Vec3i& chunkCoord, + MultiBlockChange& out) const { + auto it = dirty.find(chunkCoord); + if (it == dirty.end() || it->second.empty()) return false; + const Chunk* chunk = world.chunkAt(chunkCoord); + if (!chunk) return false; + + out.chunkX = chunkCoord.x; + out.chunkY = chunkCoord.y; + out.chunkZ = chunkCoord.z; + out.changes.clear(); + out.changes.reserve(it->second.size()); + const std::array& blocks = chunk->raw(); + for (int index : it->second) + out.changes.push_back({static_cast(index), blocks[static_cast(index)]}); + return true; +} + +bool ServerChunkSync::shouldResendFull(const Vec3i& chunkCoord, std::size_t threshold) const { + return dirtyCount(chunkCoord) >= threshold; +} + +void ClientChunkSync::applyFull(const ChunkData& packet, World& world) { + Chunk& chunk = world.ensureChunk({packet.chunkX, packet.chunkY, packet.chunkZ}); + packet.toChunk(chunk); +} + +void ClientChunkSync::applyDelta(const MultiBlockChange& packet, World& world) { + Chunk& chunk = world.ensureChunk({packet.chunkX, packet.chunkY, packet.chunkZ}); + std::array& blocks = chunk.raw(); + for (const MultiBlockChange::Change& change : packet.changes) + blocks[change.localIndex] = change.block; +} + +void ClientChunkSync::applyBlockChange(const BlockChange& packet, World& world) { + world.setBlock(packet.x, packet.y, packet.z, packet.block); +} + +} // namespace vf