#pragma once #include #include #include #include #include #include "NetSession.h" namespace vf { struct ServerConfig { uint32_t maxClients = 8; int viewRadius = 2; uint32_t chunksPerClientPerTick = 2; std::size_t bytesPerClientPerTick = 0; uint32_t keepAliveIntervalTicks = 20; uint32_t keepAliveTimeoutTicks = 100; std::size_t multiBlockThreshold = 4; int32_t protocolVersion = 47; uint64_t seedHash = 0; std::string levelType = "default"; TransportConfig transport; LinkQuality link; }; struct ServerStats { uint32_t connectedClients = 0; uint64_t bytesSentThisTick = 0; uint64_t totalBytesSent = 0; uint64_t chunksSent = 0; uint64_t blockUpdatesSent = 0; uint64_t queuedPackets = 0; uint64_t queuedBytes = 0; uint64_t acceptedConnections = 0; uint64_t rejectedConnections = 0; uint64_t timeouts = 0; uint64_t editsApplied = 0; }; // One connected player: its own link, its own transport, its own session. The // slot outlives the connection so the index handed out at accept stays valid // until the slot is reused. struct ClientSlot { bool active = false; int32_t entityId = 0; uint64_t lastServicedTick = 0; uint64_t lastBytesFlushed = 0; uint64_t lastChunksSent = 0; uint64_t lastBlockUpdates = 0; std::unique_ptr link; std::unique_ptr transport; std::unique_ptr session; }; // Many clients over one authoritative world. Every block edit is applied once // and then offered to each session, which decides on its own whether that // client is close enough to care. class NetServer { public: NetServer(World& world, ServerConfig config = {}); // The slot index, or -1 when every slot is taken. int acceptClient(); void dropClient(int slot); LoopbackLink* linkFor(int slot); NetSession* sessionFor(int slot); const ClientSlot& slotAt(int slot) const { return slots[static_cast(slot)]; } std::size_t slotCount() const { return slots.size(); } uint32_t activeClients() const; bool isActive(int slot) const; // Authoritative edit: the world, the dirty feed and every session's work // list move together, so no client can be told about a block the world // does not already have. void setBlock(int gx, int gy, int gz, Block block); void moveEntity(int32_t entityId, const Vec3& position, float yaw, float pitch, bool onGround); void update(uint64_t tick); World& world() { return *worldRef; } ServerChunkSync& chunkSync() { return sync; } const ServerConfig& settings() const { return config; } const ServerStats& stats() const { return statistics; } private: void applyEdit(const BlockChange& change); void collectStats(); void reap(); World* worldRef; ServerConfig config; ServerChunkSync sync; std::vector slots; ServerStats statistics; }; } // namespace vf