feat(core): add chunk dirty-tracking and network sync batching
This commit is contained in:
1 parent
94d876042e
commit
2aaa05f860
2 files changed
+117
No files matched your search
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#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<Vec3i, std::set<int>, 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
|
||||||
@@ -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<Block, chunkVolume>& blocks = chunk->raw();
|
||||||
|
for (int index : it->second)
|
||||||
|
out.changes.push_back({static_cast<uint16_t>(index), blocks[static_cast<std::size_t>(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<Block, chunkVolume>& 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
|
||||||
Reference in new issue
Block a user