feat: add chunkOfPosition
This commit is contained in:
1 parent
20aecc6757
commit
db6bdb8dc2
1 file changed
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#include "vf/NetServer.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Vec3i chunkOfPosition(const Vec3& position) {
|
||||||
|
int gx = static_cast<int>(std::floor(position.x));
|
||||||
|
int gy = static_cast<int>(std::floor(position.y));
|
||||||
|
int gz = static_cast<int>(std::floor(position.z));
|
||||||
|
return Vec3i{floorDiv(gx, chunkSize), floorDiv(gy, chunkSize), floorDiv(gz, chunkSize)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NetServer::NetServer(World& world, ServerConfig config) : worldRef(&world), config(config) {
|
||||||
|
if (this->config.maxClients == 0) this->config.maxClients = 1;
|
||||||
|
slots.resize(this->config.maxClients);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t NetServer::activeClients() const {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (const ClientSlot& slot : slots)
|
||||||
|
if (slot.active) ++count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetServer::isActive(int slot) const {
|
||||||
|
if (slot < 0 || static_cast<std::size_t>(slot) >= slots.size()) return false;
|
||||||
|
return slots[static_cast<std::size_t>(slot)].active;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetServer::acceptClient() {
|
||||||
|
for (std::size_t i = 0; i < slots.size(); ++i) {
|
||||||
|
ClientSlot& slot = slots[i];
|
||||||
|
if (slot.active) continue;
|
||||||
|
|
||||||
|
SessionConfig sessionConfig;
|
||||||
|
sessionConfig.protocolVersion = config.protocolVersion;
|
||||||
|
sessionConfig.viewRadius = config.viewRadius;
|
||||||
|
sessionConfig.chunksPerTick = config.chunksPerClientPerTick;
|
||||||
|
sessionConfig.keepAliveIntervalTicks = config.keepAliveIntervalTicks;
|
||||||
|
sessionConfig.keepAliveTimeoutTicks = config.keepAliveTimeoutTicks;
|
||||||
|
sessionConfig.multiBlockThreshold = config.multiBlockThreshold;
|
||||||
|
sessionConfig.bandwidthBytesPerTick = config.bytesPerClientPerTick;
|
||||||
|
sessionConfig.maxPlayers = static_cast<uint8_t>(config.maxClients);
|
||||||
|
sessionConfig.seedHash = config.seedHash;
|
||||||
|
sessionConfig.levelType = config.levelType;
|
||||||
|
|
||||||
|
slot.active = true;
|
||||||
|
slot.entityId = static_cast<int32_t>(i) + 1;
|
||||||
|
slot.lastBytesFlushed = 0;
|
||||||
|
slot.lastChunksSent = 0;
|
||||||
|
slot.lastBlockUpdates = 0;
|
||||||
|
slot.link.reset(new LoopbackLink(config.link));
|
||||||
|
slot.transport.reset(new Transport(*slot.link, 0, config.transport));
|
||||||
|
slot.session.reset(new NetSession(*slot.transport, NetSession::Role::Server, sessionConfig));
|
||||||
|
slot.session->attachWorld(*worldRef);
|
||||||
|
slot.session->attachChunkSync(sync);
|
||||||
|
slot.session->setEntityId(slot.entityId);
|
||||||
|
|
||||||
|
++statistics.acceptedConnections;
|
||||||
|
return static_cast<int>(i);
|
||||||
|
}
|
||||||
|
++statistics.rejectedConnections;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::dropClient(int slot) {
|
||||||
|
if (!isActive(slot)) return;
|
||||||
|
ClientSlot& entry = slots[static_cast<std::size_t>(slot)];
|
||||||
|
entry.active = false;
|
||||||
|
entry.session.reset();
|
||||||
|
entry.transport.reset();
|
||||||
|
entry.link.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoopbackLink* NetServer::linkFor(int slot) {
|
||||||
|
if (!isActive(slot)) return nullptr;
|
||||||
|
return slots[static_cast<std::size_t>(slot)].link.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetSession* NetServer::sessionFor(int slot) {
|
||||||
|
if (!isActive(slot)) return nullptr;
|
||||||
|
return slots[static_cast<std::size_t>(slot)].session.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::applyEdit(const BlockChange& change) {
|
||||||
|
worldRef->setBlock(change.x, change.y, change.z, change.block);
|
||||||
|
Vec3i coord{floorDiv(change.x, chunkSize), floorDiv(change.y, chunkSize),
|
||||||
|
floorDiv(change.z, chunkSize)};
|
||||||
|
sync.markDirty(coord, floorMod(change.x, chunkSize), floorMod(change.y, chunkSize),
|
||||||
|
floorMod(change.z, chunkSize));
|
||||||
|
for (ClientSlot& slot : slots)
|
||||||
|
if (slot.active) slot.session->markChunkDirty(coord);
|
||||||
|
++statistics.editsApplied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::setBlock(int gx, int gy, int gz, Block block) {
|
||||||
|
BlockChange change;
|
||||||
|
change.x = gx;
|
||||||
|
change.y = gy;
|
||||||
|
change.z = gz;
|
||||||
|
change.block = block;
|
||||||
|
applyEdit(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::moveEntity(int32_t entityId, const Vec3& position, float yaw, float pitch,
|
||||||
|
bool onGround) {
|
||||||
|
Vec3i coord = chunkOfPosition(position);
|
||||||
|
for (ClientSlot& slot : slots) {
|
||||||
|
if (!slot.active) continue;
|
||||||
|
if (slot.session->phase() != SessionPhase::Play) continue;
|
||||||
|
// An entity outside the client's view costs nothing to skip and cannot
|
||||||
|
// be drawn there anyway.
|
||||||
|
if (!slot.session->view().tracker.contains(coord)) continue;
|
||||||
|
slot.session->sendEntityMove(entityId, position, yaw, pitch, onGround);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::collectStats() {
|
||||||
|
statistics.bytesSentThisTick = 0;
|
||||||
|
statistics.queuedPackets = 0;
|
||||||
|
statistics.queuedBytes = 0;
|
||||||
|
for (ClientSlot& slot : slots) {
|
||||||
|
if (!slot.active) continue;
|
||||||
|
const SessionStats& sessionStats = slot.session->stats();
|
||||||
|
|
||||||
|
uint64_t bytes = sessionStats.bytesFlushed - slot.lastBytesFlushed;
|
||||||
|
slot.lastBytesFlushed = sessionStats.bytesFlushed;
|
||||||
|
statistics.bytesSentThisTick += bytes;
|
||||||
|
statistics.totalBytesSent += bytes;
|
||||||
|
|
||||||
|
uint64_t chunks = sessionStats.chunksSent - slot.lastChunksSent;
|
||||||
|
slot.lastChunksSent = sessionStats.chunksSent;
|
||||||
|
statistics.chunksSent += chunks;
|
||||||
|
|
||||||
|
uint64_t updates = sessionStats.blockChangesSent + sessionStats.multiBlockChangesSent;
|
||||||
|
statistics.blockUpdatesSent += updates - slot.lastBlockUpdates;
|
||||||
|
slot.lastBlockUpdates = updates;
|
||||||
|
|
||||||
|
statistics.queuedPackets += slot.session->queuedPacketCount();
|
||||||
|
statistics.queuedBytes += slot.session->queuedByteCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::reap() {
|
||||||
|
for (std::size_t i = 0; i < slots.size(); ++i) {
|
||||||
|
ClientSlot& slot = slots[i];
|
||||||
|
if (!slot.active) continue;
|
||||||
|
if (slot.session->phase() != SessionPhase::Disconnected) continue;
|
||||||
|
if (slot.session->timedOut()) ++statistics.timeouts;
|
||||||
|
dropClient(static_cast<int>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetServer::update(uint64_t tick) {
|
||||||
|
for (ClientSlot& slot : slots) {
|
||||||
|
if (!slot.active) continue;
|
||||||
|
slot.session->receive(tick);
|
||||||
|
slot.lastServicedTick = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upstream edits land in the world before any session builds its outgoing
|
||||||
|
// packets, so every client sees the same authoritative state this tick.
|
||||||
|
for (ClientSlot& slot : slots) {
|
||||||
|
if (!slot.active) continue;
|
||||||
|
for (const BlockChange& change : slot.session->requestedEdits()) applyEdit(change);
|
||||||
|
slot.session->clearRequestedEdits();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ClientSlot& slot : slots) {
|
||||||
|
if (!slot.active) continue;
|
||||||
|
slot.session->send(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
sync.clearAll();
|
||||||
|
collectStats();
|
||||||
|
reap();
|
||||||
|
statistics.connectedClients = activeClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
Reference in new issue
Block a user