From: Daniel Karbach Date: Fri, 18 Sep 2015 13:01:21 +0000 (+0200) Subject: move server and client stuff around X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=8ae45b6555d55f301f83daf8c1337d332d8305ab;p=blank.git move server and client stuff around --- diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index 65ad408..36808a0 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -2,8 +2,8 @@ #define BLANK_RUNTIME_HPP_ #include "Environment.hpp" -#include "../net/Client.hpp" -#include "../net/Server.hpp" +#include "../client/Client.hpp" +#include "../server/Server.hpp" #include "../ui/Interface.hpp" #include "../world/Generator.hpp" #include "../world/World.hpp" @@ -44,11 +44,11 @@ public: bool doublebuf = true; int multisampling = 1; - Client::Config client = Client::Config(); + client::Client::Config client = client::Client::Config(); Generator::Config gen = Generator::Config(); HeadlessEnvironment::Config env = HeadlessEnvironment::Config(); Interface::Config interface = Interface::Config(); - Server::Config server = Server::Config(); + server::Server::Config server = server::Server::Config(); World::Config world = World::Config(); }; diff --git a/src/app/ServerState.cpp b/src/app/ServerState.cpp deleted file mode 100644 index 13abdc9..0000000 --- a/src/app/ServerState.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "ServerState.hpp" - -#include "Environment.hpp" -#include "TextureIndex.hpp" -#include "../net/io.hpp" - -#include - - -namespace blank { - -ServerState::ServerState( - HeadlessEnvironment &env, - const Generator::Config &gc, - const World::Config &wc, - const WorldSave &ws, - const Server::Config &sc -) -: env(env) -, block_types() -, world(block_types, wc) -, generator(gc) -, chunk_loader(world.Chunks(), generator, ws) -, skeletons() -, spawner(world, skeletons, gc.seed) -, server(sc, world) -, loop_timer(16) { - TextureIndex tex_index; - env.loader.LoadBlockTypes("default", block_types, tex_index); - skeletons.LoadHeadless(); - - loop_timer.Start(); - - std::cout << "listening on UDP port " << sc.port << std::endl; -} - - -void ServerState::Handle(const SDL_Event &event) { - if (event.type == SDL_QUIT) { - env.state.PopAll(); - } -} - - -void ServerState::Update(int dt) { - loop_timer.Update(dt); - server.Handle(); - int world_dt = 0; - while (loop_timer.HitOnce()) { - spawner.Update(loop_timer.Interval()); - world.Update(loop_timer.Interval()); - world_dt += loop_timer.Interval(); - loop_timer.PopIteration(); - } - chunk_loader.Update(dt); - if (world_dt > 0) { - server.Update(world_dt); - } -} - - -void ServerState::Render(Viewport &viewport) { - -} - -} diff --git a/src/app/ServerState.hpp b/src/app/ServerState.hpp deleted file mode 100644 index db21d0f..0000000 --- a/src/app/ServerState.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef BLANK_APP_SERVERSTATE_HPP_ -#define BLANK_APP_SERVERSTATE_HPP_ - -#include "IntervalTimer.hpp" -#include "State.hpp" -#include "../ai/Spawner.hpp" -#include "../model/Skeletons.hpp" -#include "../net/Server.hpp" -#include "../world/BlockTypeRegistry.hpp" -#include "../world/ChunkLoader.hpp" -#include "../world/Generator.hpp" -#include "../world/World.hpp" - - -namespace blank { - -class HeadlessEnvironment; -class WorldSave; - -class ServerState -: public State { - -public: - ServerState( - HeadlessEnvironment &, - const Generator::Config &, - const World::Config &, - const WorldSave &, - const Server::Config & - ); - - void Handle(const SDL_Event &) override; - void Update(int dt) override; - void Render(Viewport &) override; - -private: - HeadlessEnvironment &env; - BlockTypeRegistry block_types; - World world; - Generator generator; - ChunkLoader chunk_loader; - Skeletons skeletons; - Spawner spawner; - Server server; - IntervalTimer loop_timer; - -}; - -} - -#endif diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 8ce7b5d..cc9d3bb 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -1,13 +1,13 @@ #include "Application.hpp" #include "Environment.hpp" #include "Runtime.hpp" -#include "ServerState.hpp" #include "WorldState.hpp" #include "init.hpp" #include "../client/MasterState.hpp" #include "../io/filesystem.hpp" #include "../io/WorldSave.hpp" +#include "../server/ServerState.hpp" #include #include @@ -342,7 +342,7 @@ void Runtime::RunServer() { } HeadlessApplication app(env); - ServerState server_state(env, config.gen, config.world, save, config.server); + server::ServerState server_state(env, config.gen, config.world, save, config.server); app.PushState(&server_state); Run(app); } diff --git a/src/client/ChunkReceiver.hpp b/src/client/ChunkReceiver.hpp new file mode 100644 index 0000000..74a03a7 --- /dev/null +++ b/src/client/ChunkReceiver.hpp @@ -0,0 +1,44 @@ +#ifndef BLANK_CLIENT_CHUNKRECEIVER_HPP_ +#define BLANK_CLIENT_CHUNKRECEIVER_HPP_ + +#include "../app/IntervalTimer.hpp" +#include "../net/Packet.hpp" + +#include +#include + + +namespace blank { + +class ChunkStore; + +namespace client { + +class ChunkTransmission; + +class ChunkReceiver { + +public: + explicit ChunkReceiver(ChunkStore &); + ~ChunkReceiver(); + + void Update(int dt); + + void Handle(const Packet::ChunkBegin &); + void Handle(const Packet::ChunkData &); + +private: + ChunkTransmission &GetTransmission(std::uint32_t id); + void Commit(ChunkTransmission &); + +private: + ChunkStore &store; + std::list transmissions; + IntervalTimer timer; + +}; + +} +} + +#endif diff --git a/src/client/ChunkTransmission.hpp b/src/client/ChunkTransmission.hpp new file mode 100644 index 0000000..f54c7d8 --- /dev/null +++ b/src/client/ChunkTransmission.hpp @@ -0,0 +1,42 @@ +#ifndef BLANK_CLIENT_CHUNKTRANSMISSION_HPP_ +#define BLANK_CLIENT_CHUNKTRANSMISSION_HPP_ + +#include "../world/Chunk.hpp" + +#include +#include + + +namespace blank { +namespace client { + +struct ChunkTransmission { + + std::uint32_t id; + std::uint32_t flags; + glm::ivec3 coords; + std::uint32_t data_size; + std::uint32_t data_received; + + int last_update; + + bool header_received; + bool active; + + std::uint8_t buffer[Chunk::BlockSize() + 10]; + + + ChunkTransmission(); + + void Clear() noexcept; + + bool Complete() const noexcept; + + bool Compressed() const noexcept; + +}; + +} +} + +#endif diff --git a/src/client/Client.hpp b/src/client/Client.hpp new file mode 100644 index 0000000..68b2f7f --- /dev/null +++ b/src/client/Client.hpp @@ -0,0 +1,53 @@ +#ifndef BLANK_CLIENT_CLIENT_HPP_ +#define BLANK_CLIENT_CLIENT_HPP_ + +#include "../net/Connection.hpp" + +#include +#include + + +namespace blank { + +class World; + +namespace client { + +class Client { + +public: + struct Config { + std::string host = "localhost"; + Uint16 port = 12354; + }; + +public: + explicit Client(const Config &); + ~Client(); + + void Handle(); + + void Update(int dt); + + Connection &GetConnection() noexcept { return conn; } + const Connection &GetConnection() const noexcept { return conn; } + + std::uint16_t SendPing(); + std::uint16_t SendLogin(const std::string &); + std::uint16_t SendPart(); + std::uint16_t SendPlayerUpdate(const Entity &); + +private: + void HandlePacket(const UDPpacket &); + +private: + Connection conn; + UDPsocket client_sock; + UDPpacket client_pack; + +}; + +} +} + +#endif diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index 77e02c3..8cf9246 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -1,12 +1,12 @@ #ifndef BLANK_CLIENT_INTERACTIVESTATE_HPP_ #define BLANK_CLIENT_INTERACTIVESTATE_HPP_ +#include "ChunkReceiver.hpp" #include "ChunkRequester.hpp" #include "../app/IntervalTimer.hpp" #include "../app/State.hpp" #include "../io/WorldSave.hpp" #include "../model/Skeletons.hpp" -#include "../net/ChunkReceiver.hpp" #include "../ui/Interface.hpp" #include "../world/BlockTypeRegistry.hpp" #include "../world/ChunkRenderer.hpp" diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp index ff1769f..50773a6 100644 --- a/src/client/MasterState.hpp +++ b/src/client/MasterState.hpp @@ -1,10 +1,10 @@ #ifndef BLANK_CLIENT_CLIENTSTATE_HPP_ #define BLANK_CLIENT_CLIENTSTATE_HPP_ +#include "Client.hpp" #include "InitialState.hpp" #include "InteractiveState.hpp" #include "../app/State.hpp" -#include "../net/Client.hpp" #include "../net/ConnectionHandler.hpp" #include diff --git a/src/client/net.cpp b/src/client/net.cpp new file mode 100644 index 0000000..ed2dd20 --- /dev/null +++ b/src/client/net.cpp @@ -0,0 +1,250 @@ +#include "ChunkReceiver.hpp" +#include "ChunkTransmission.hpp" +#include "Client.hpp" + +#include "../app/init.hpp" +#include "../net/Packet.hpp" +#include "../world/Chunk.hpp" +#include "../world/ChunkStore.hpp" + +#include +#include +#include + +using namespace std; + + +namespace blank { +namespace client { + + +ChunkReceiver::ChunkReceiver(ChunkStore &store) +: store(store) +, transmissions() +, timer(5000) { + timer.Start(); +} + +ChunkReceiver::~ChunkReceiver() { + +} + +void ChunkReceiver::Update(int dt) { + timer.Update(dt); + for (ChunkTransmission &trans : transmissions) { + if (trans.active && (timer.Elapsed() - trans.last_update) > timer.Interval()) { + cout << "timeout for transmission of chunk " << trans.coords << endl; + trans.Clear(); + } + } + if (transmissions.size() > 3) { + for (auto iter = transmissions.begin(), end = transmissions.end(); iter != end; ++iter) { + if (!iter->active) { + transmissions.erase(iter); + break; + } + } + } +} + +void ChunkReceiver::Handle(const Packet::ChunkBegin &pack) { + uint32_t id; + pack.ReadTransmissionId(id); + ChunkTransmission &trans = GetTransmission(id); + pack.ReadFlags(trans.flags); + pack.ReadChunkCoords(trans.coords); + pack.ReadDataSize(trans.data_size); + trans.last_update = timer.Elapsed(); + trans.header_received = true; + Commit(trans); +} + +void ChunkReceiver::Handle(const Packet::ChunkData &pack) { + uint32_t id, pos, size; + pack.ReadTransmissionId(id); + pack.ReadDataOffset(pos); + if (pos >= sizeof(ChunkTransmission::buffer)) { + cout << "received chunk data offset outside of buffer size" << endl; + return; + } + pack.ReadDataSize(size); + ChunkTransmission &trans = GetTransmission(id); + size_t len = min(size_t(size), sizeof(ChunkTransmission::buffer) - pos); + pack.ReadData(&trans.buffer[pos], len); + // TODO: this method breaks when a packet arrives twice + trans.data_received += len; + trans.last_update = timer.Elapsed(); + Commit(trans); +} + +ChunkTransmission &ChunkReceiver::GetTransmission(uint32_t id) { + // search for ongoing + for (ChunkTransmission &trans : transmissions) { + if (trans.active && trans.id == id) { + return trans; + } + } + // search for unused + for (ChunkTransmission &trans : transmissions) { + if (!trans.active) { + trans.active = true; + trans.id = id; + return trans; + } + } + // allocate new + transmissions.emplace_back(); + transmissions.back().active = true; + transmissions.back().id = id; + return transmissions.back(); +} + +void ChunkReceiver::Commit(ChunkTransmission &trans) { + if (!trans.Complete()) return; + + Chunk *chunk = store.Allocate(trans.coords); + if (!chunk) { + // chunk no longer of interes, just drop the data + // it should probably be cached to disk, but not now :P + trans.Clear(); + return; + } + + const Byte *src = &trans.buffer[0]; + uLong src_len = min(size_t(trans.data_size), sizeof(ChunkTransmission::buffer)); + Byte *dst = reinterpret_cast(chunk->BlockData()); + uLong dst_len = Chunk::BlockSize(); + + if (trans.Compressed()) { + if (uncompress(dst, &dst_len, src, src_len) != Z_OK) { + // omg, now what? + cout << "got corruped chunk data for " << trans.coords << endl; + } + } else { + memcpy(dst, src, min(src_len, dst_len)); + } + trans.Clear(); +} + +ChunkTransmission::ChunkTransmission() +: id(0) +, flags(0) +, coords() +, data_size(0) +, data_received(0) +, last_update(0) +, header_received(false) +, active(false) +, buffer() { + +} + +void ChunkTransmission::Clear() noexcept { + data_size = 0; + data_received = 0; + last_update = 0; + header_received = false; + active = false; +} + +bool ChunkTransmission::Complete() const noexcept { + return header_received && data_received == data_size; +} + +bool ChunkTransmission::Compressed() const noexcept { + return flags & 1; +} + + +namespace { + +UDPsocket client_bind(Uint16 port) { + UDPsocket sock = SDLNet_UDP_Open(port); + if (!sock) { + throw NetError("SDLNet_UDP_Open"); + } + return sock; +} + +IPaddress client_resolve(const char *host, Uint16 port) { + IPaddress addr; + if (SDLNet_ResolveHost(&addr, host, port) != 0) { + throw NetError("SDLNet_ResolveHost"); + } + return addr; +} + +} + +Client::Client(const Config &conf) +: conn(client_resolve(conf.host.c_str(), conf.port)) +, client_sock(client_bind(0)) +, client_pack{ -1, nullptr, 0 } { + client_pack.data = new Uint8[sizeof(Packet)]; + client_pack.maxlen = sizeof(Packet); + // establish connection + SendPing(); +} + +Client::~Client() { + delete[] client_pack.data; + SDLNet_UDP_Close(client_sock); +} + + +void Client::Handle() { + int result = SDLNet_UDP_Recv(client_sock, &client_pack); + while (result > 0) { + HandlePacket(client_pack); + result = SDLNet_UDP_Recv(client_sock, &client_pack); + } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); + } +} + +void Client::HandlePacket(const UDPpacket &udp_pack) { + if (!conn.Matches(udp_pack.address)) { + // packet came from somewhere else, drop + return; + } + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop + return; + } + + conn.Received(udp_pack); +} + +void Client::Update(int dt) { + conn.Update(dt); + if (conn.ShouldPing()) { + SendPing(); + } +} + +uint16_t Client::SendPing() { + return conn.SendPing(client_pack, client_sock); +} + +uint16_t Client::SendLogin(const string &name) { + auto pack = Packet::Make(client_pack); + pack.WritePlayerName(name); + return conn.Send(client_pack, client_sock); +} + +uint16_t Client::SendPlayerUpdate(const Entity &player) { + auto pack = Packet::Make(client_pack); + pack.WritePlayer(player); + return conn.Send(client_pack, client_sock); +} + +uint16_t Client::SendPart() { + Packet::Make(client_pack); + return conn.Send(client_pack, client_sock); +} + +} +} diff --git a/src/net/ChunkReceiver.hpp b/src/net/ChunkReceiver.hpp deleted file mode 100644 index 40acc2f..0000000 --- a/src/net/ChunkReceiver.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef BLANK_NET_CHUNKRECEIVER_HPP_ -#define BLANK_NET_CHUNKRECEIVER_HPP_ - -#include "Packet.hpp" -#include "../app/IntervalTimer.hpp" - -#include -#include - - -namespace blank { - -class ChunkStore; -class ChunkTransmission; - -class ChunkReceiver { - -public: - explicit ChunkReceiver(ChunkStore &); - ~ChunkReceiver(); - - void Update(int dt); - - void Handle(const Packet::ChunkBegin &); - void Handle(const Packet::ChunkData &); - -private: - ChunkTransmission &GetTransmission(std::uint32_t id); - void Commit(ChunkTransmission &); - -private: - ChunkStore &store; - std::list transmissions; - IntervalTimer timer; - -}; - -} - -#endif diff --git a/src/net/ChunkTransmission.hpp b/src/net/ChunkTransmission.hpp deleted file mode 100644 index 90dd35e..0000000 --- a/src/net/ChunkTransmission.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_ -#define BLANK_NET_CHUNKTRANSMISSION_HPP_ - -#include "../world/Chunk.hpp" - -#include -#include - - -namespace blank { - -struct ChunkTransmission { - - std::uint32_t id; - std::uint32_t flags; - glm::ivec3 coords; - std::uint32_t data_size; - std::uint32_t data_received; - - int last_update; - - bool header_received; - bool active; - - std::uint8_t buffer[Chunk::BlockSize() + 10]; - - - ChunkTransmission(); - - void Clear() noexcept; - - bool Complete() const noexcept; - - bool Compressed() const noexcept; - -}; - -}; - -#endif diff --git a/src/net/ChunkTransmitter.hpp b/src/net/ChunkTransmitter.hpp deleted file mode 100644 index 44f6bd8..0000000 --- a/src/net/ChunkTransmitter.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef BLANK_NET_CHUNKTRANSMITTER_HPP_ -#define BLANK_NET_CHUNKTRANSMITTER_HPP_ - -#include -#include -#include - - -namespace blank { - -class Chunk; -class ClientConnection; - -class ChunkTransmitter { - -public: - explicit ChunkTransmitter(ClientConnection &); - ~ChunkTransmitter(); - - /// Returns true if not transmitting or waiting on acks, so - /// the next chunk may be queued without schmutzing up anything. - bool Idle() const noexcept; - - /// Returns true if a transmission is still going on, - /// meaning there's at least one packet that needs to - /// be sent. - bool Transmitting() const noexcept; - /// Send the next packet of the current chunk (if any). - void Transmit(); - - /// Returns true if there's one or more packets which - /// still have to be ack'd by the remote. - bool Waiting() const noexcept; - /// Mark packet with given sequence number as ack'd. - /// If all packets for the current chunk have been ack'd - /// the transmission is considered complete. - void Ack(std::uint16_t); - /// Mark packet with given sequence number as lost. - /// Its part of the chunk data should be resent. - void Nack(std::uint16_t); - - /// Cancel the current transmission. - void Abort(); - /// Start transmitting given chunk. - /// If there's a chunk already in transmission it will be - /// cancelled. - void Send(Chunk &); - -private: - void SendBegin(); - void SendData(std::size_t); - void Release(); - -private: - ClientConnection &conn; - Chunk *current; - std::size_t buffer_size; - std::unique_ptr buffer; - std::size_t buffer_len; - std::size_t packet_len; - std::size_t cursor; - std::size_t num_packets; - int begin_packet; - std::vector data_packets; - int confirm_wait; - std::uint32_t trans_id; - bool compressed; - -}; - -} - -#endif diff --git a/src/net/Client.hpp b/src/net/Client.hpp deleted file mode 100644 index 2848aed..0000000 --- a/src/net/Client.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef BLANK_NET_CLIENT_HPP_ -#define BLANK_NET_CLIENT_HPP_ - -#include "Connection.hpp" - -#include -#include - - -namespace blank { - -class World; - -class Client { - -public: - struct Config { - std::string host = "localhost"; - Uint16 port = 12354; - }; - -public: - explicit Client(const Config &); - ~Client(); - - void Handle(); - - void Update(int dt); - - Connection &GetConnection() noexcept { return conn; } - const Connection &GetConnection() const noexcept { return conn; } - - std::uint16_t SendPing(); - std::uint16_t SendLogin(const std::string &); - std::uint16_t SendPart(); - std::uint16_t SendPlayerUpdate(const Entity &); - -private: - void HandlePacket(const UDPpacket &); - -private: - Connection conn; - UDPsocket client_sock; - UDPpacket client_pack; - -}; - -} - -#endif diff --git a/src/net/ClientConnection.hpp b/src/net/ClientConnection.hpp deleted file mode 100644 index 075fdce..0000000 --- a/src/net/ClientConnection.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef BLANK_NET_CLIENTCONNECTION_HPP_ -#define BLANK_NET_CLIENTCONNECTION_HPP_ - -#include "ChunkTransmitter.hpp" -#include "Connection.hpp" -#include "ConnectionHandler.hpp" -#include "Server.hpp" -#include "../app/IntervalTimer.hpp" -#include "../world/EntityState.hpp" -#include "../world/Player.hpp" - -#include -#include -#include - - -namespace blank { - -class Server; - -class ClientConnection -: public ConnectionHandler { - -public: - explicit ClientConnection(Server &, const IPaddress &); - ~ClientConnection(); - - bool Matches(const IPaddress &addr) const noexcept { return conn.Matches(addr); } - - void Update(int dt); - - Connection &GetConnection() noexcept { return conn; } - bool Disconnected() const noexcept { return conn.Closed(); } - - /// prepare a packet of given type - template - Type Prepare() const noexcept { - return Packet::Make(server.GetPacket()); - } - /// send the previously prepared packet - std::uint16_t Send(); - /// send the previously prepared packet of non-default length - std::uint16_t Send(std::size_t len); - - void AttachPlayer(const Player &); - void DetachPlayer(); - bool HasPlayer() const noexcept { return player.entity; } - Entity &PlayerEntity() noexcept { return *player.entity; } - const Entity &PlayerEntity() const noexcept { return *player.entity; } - ChunkIndex &PlayerChunks() noexcept { return *player.chunks; } - const ChunkIndex &PlayerChunks() const noexcept { return *player.chunks; } - -private: - struct SpawnStatus { - // the entity in question - Entity *const entity = nullptr; - // sequence number of the spawn packet or -1 after it's been ack'd - std::int32_t spawn_pack = -1; - // sequence number of the despawn packet or -1 if no despawn has been sent - std::int32_t despawn_pack = -1; - - explicit SpawnStatus(Entity &); - ~SpawnStatus(); - }; - -private: - void OnPacketReceived(std::uint16_t) override; - void OnPacketLost(std::uint16_t) override; - - void On(const Packet::Login &) override; - void On(const Packet::Part &) override; - void On(const Packet::PlayerUpdate &) override; - - bool CanSpawn(const Entity &) const noexcept; - bool CanDespawn(const Entity &) const noexcept; - - void SendSpawn(SpawnStatus &); - void SendDespawn(SpawnStatus &); - void SendUpdate(SpawnStatus &); - - void CheckPlayerFix(); - - void CheckChunkQueue(); - -private: - Server &server; - Connection conn; - Player player; - std::list spawns; - unsigned int confirm_wait; - - EntityState player_update_state; - std::uint16_t player_update_pack; - IntervalTimer player_update_timer; - - ChunkTransmitter transmitter; - std::deque chunk_queue; - glm::ivec3 old_base; - -}; - -} - -#endif diff --git a/src/net/Server.hpp b/src/net/Server.hpp deleted file mode 100644 index a6d509b..0000000 --- a/src/net/Server.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef BLANK_NET_SERVER_HPP -#define BLANK_NET_SERVER_HPP - -#include -#include - - -namespace blank { - -class ClientConnection; -class World; - -class Server { - -public: - struct Config { - Uint16 port = 12354; - }; - -public: - Server(const Config &, World &); - ~Server(); - - void Handle(); - - void Update(int dt); - - UDPsocket &GetSocket() noexcept { return serv_sock; } - UDPpacket &GetPacket() noexcept { return serv_pack; } - - World &GetWorld() noexcept { return world; } - -private: - void HandlePacket(const UDPpacket &); - - ClientConnection &GetClient(const IPaddress &); - -private: - UDPsocket serv_sock; - UDPpacket serv_pack; - std::list clients; - - World &world; - -}; - -} - -#endif diff --git a/src/net/chunk.cpp b/src/net/chunk.cpp deleted file mode 100644 index 96e5ce6..0000000 --- a/src/net/chunk.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include "ChunkReceiver.hpp" -#include "ChunkTransmission.hpp" -#include "ChunkTransmitter.hpp" - -#include "ClientConnection.hpp" -#include "Packet.hpp" -#include "../world/Chunk.hpp" -#include "../world/ChunkStore.hpp" - -#include -#include -#include - -using namespace std; - - -namespace blank { - -ChunkReceiver::ChunkReceiver(ChunkStore &store) -: store(store) -, transmissions() -, timer(5000) { - timer.Start(); -} - -ChunkReceiver::~ChunkReceiver() { - -} - -void ChunkReceiver::Update(int dt) { - timer.Update(dt); - for (ChunkTransmission &trans : transmissions) { - if (trans.active && (timer.Elapsed() - trans.last_update) > timer.Interval()) { - cout << "timeout for transmission of chunk " << trans.coords << endl; - trans.Clear(); - } - } - if (transmissions.size() > 3) { - for (auto iter = transmissions.begin(), end = transmissions.end(); iter != end; ++iter) { - if (!iter->active) { - transmissions.erase(iter); - break; - } - } - } -} - -void ChunkReceiver::Handle(const Packet::ChunkBegin &pack) { - uint32_t id; - pack.ReadTransmissionId(id); - ChunkTransmission &trans = GetTransmission(id); - pack.ReadFlags(trans.flags); - pack.ReadChunkCoords(trans.coords); - pack.ReadDataSize(trans.data_size); - trans.last_update = timer.Elapsed(); - trans.header_received = true; - Commit(trans); -} - -void ChunkReceiver::Handle(const Packet::ChunkData &pack) { - uint32_t id, pos, size; - pack.ReadTransmissionId(id); - pack.ReadDataOffset(pos); - if (pos >= sizeof(ChunkTransmission::buffer)) { - cout << "received chunk data offset outside of buffer size" << endl; - return; - } - pack.ReadDataSize(size); - ChunkTransmission &trans = GetTransmission(id); - size_t len = min(size_t(size), sizeof(ChunkTransmission::buffer) - pos); - pack.ReadData(&trans.buffer[pos], len); - // TODO: this method breaks when a packet arrives twice - trans.data_received += len; - trans.last_update = timer.Elapsed(); - Commit(trans); -} - -ChunkTransmission &ChunkReceiver::GetTransmission(uint32_t id) { - // search for ongoing - for (ChunkTransmission &trans : transmissions) { - if (trans.active && trans.id == id) { - return trans; - } - } - // search for unused - for (ChunkTransmission &trans : transmissions) { - if (!trans.active) { - trans.active = true; - trans.id = id; - return trans; - } - } - // allocate new - transmissions.emplace_back(); - transmissions.back().active = true; - transmissions.back().id = id; - return transmissions.back(); -} - -void ChunkReceiver::Commit(ChunkTransmission &trans) { - if (!trans.Complete()) return; - - Chunk *chunk = store.Allocate(trans.coords); - if (!chunk) { - // chunk no longer of interes, just drop the data - // it should probably be cached to disk, but not now :P - trans.Clear(); - return; - } - - const Byte *src = &trans.buffer[0]; - uLong src_len = min(size_t(trans.data_size), sizeof(ChunkTransmission::buffer)); - Byte *dst = reinterpret_cast(chunk->BlockData()); - uLong dst_len = Chunk::BlockSize(); - - if (trans.Compressed()) { - if (uncompress(dst, &dst_len, src, src_len) != Z_OK) { - // omg, now what? - cout << "got corruped chunk data for " << trans.coords << endl; - } - } else { - memcpy(dst, src, min(src_len, dst_len)); - } - trans.Clear(); -} - -ChunkTransmission::ChunkTransmission() -: id(0) -, flags(0) -, coords() -, data_size(0) -, data_received(0) -, last_update(0) -, header_received(false) -, active(false) -, buffer() { - -} - -void ChunkTransmission::Clear() noexcept { - data_size = 0; - data_received = 0; - last_update = 0; - header_received = false; - active = false; -} - -bool ChunkTransmission::Complete() const noexcept { - return header_received && data_received == data_size; -} - -bool ChunkTransmission::Compressed() const noexcept { - return flags & 1; -} - - -ChunkTransmitter::ChunkTransmitter(ClientConnection &conn) -: conn(conn) -, current(nullptr) -, buffer_size(Chunk::BlockSize() + 10) -, buffer(new uint8_t[buffer_size]) -, buffer_len(0) -, packet_len(Packet::ChunkData::MAX_DATA_LEN) -, cursor(0) -, num_packets(0) -, begin_packet(-1) -, data_packets() -, confirm_wait(0) -, trans_id(0) -, compressed(false) { - -} - -ChunkTransmitter::~ChunkTransmitter() { - Abort(); -} - -bool ChunkTransmitter::Idle() const noexcept { - return !Transmitting() && !Waiting(); -} - -bool ChunkTransmitter::Transmitting() const noexcept { - return cursor < num_packets; -} - -void ChunkTransmitter::Transmit() { - if (cursor < num_packets) { - SendData(cursor); - ++cursor; - } -} - -bool ChunkTransmitter::Waiting() const noexcept { - return confirm_wait > 0; -} - -void ChunkTransmitter::Ack(uint16_t seq) { - if (!Waiting()) { - return; - } - if (seq == begin_packet) { - begin_packet = -1; - --confirm_wait; - if (Idle()) { - Release(); - } - return; - } - for (int i = 0, end = data_packets.size(); i < end; ++i) { - if (seq == data_packets[i]) { - data_packets[i] = -1; - --confirm_wait; - if (Idle()) { - Release(); - } - return; - } - } -} - -void ChunkTransmitter::Nack(uint16_t seq) { - if (!Waiting()) { - return; - } - if (seq == begin_packet) { - SendBegin(); - return; - } - for (size_t i = 0, end = data_packets.size(); i < end; ++i) { - if (seq == data_packets[i]) { - SendData(i); - return; - } - } -} - -void ChunkTransmitter::Abort() { - if (!current) return; - - Release(); - - begin_packet = -1; - data_packets.clear(); - confirm_wait = 0; -} - -void ChunkTransmitter::Send(Chunk &chunk) { - // abort current chunk, if any - Abort(); - - current = &chunk; - current->Ref(); - - // load new chunk data - compressed = true; - buffer_len = buffer_size; - if (compress(buffer.get(), &buffer_len, reinterpret_cast(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) { - // compression failed, send it uncompressed - buffer_len = Chunk::BlockSize(); - memcpy(buffer.get(), chunk.BlockData(), buffer_len); - compressed = false; - } - cursor = 0; - num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0); - data_packets.resize(num_packets, -1); - - ++trans_id; - SendBegin(); -} - -void ChunkTransmitter::SendBegin() { - uint32_t flags = compressed; - auto pack = conn.Prepare(); - pack.WriteTransmissionId(trans_id); - pack.WriteFlags(flags); - pack.WriteChunkCoords(current->Position()); - pack.WriteDataSize(buffer_len); - if (begin_packet == -1) { - ++confirm_wait; - } - begin_packet = conn.Send(); -} - -void ChunkTransmitter::SendData(size_t i) { - int pos = i * packet_len; - int len = min(packet_len, buffer_len - pos); - const uint8_t *data = &buffer[pos]; - - auto pack = conn.Prepare(); - pack.WriteTransmissionId(trans_id); - pack.WriteDataOffset(pos); - pack.WriteDataSize(len); - pack.WriteData(data, len); - - if (data_packets[i] == -1) { - ++confirm_wait; - } - data_packets[i] = conn.Send(); -} - -void ChunkTransmitter::Release() { - if (current) { - current->UnRef(); - current = nullptr; - } -} - -} diff --git a/src/net/net.cpp b/src/net/net.cpp index f59b809..81de3e4 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -1,21 +1,14 @@ -#include "Client.hpp" -#include "ClientConnection.hpp" #include "Connection.hpp" #include "ConnectionHandler.hpp" #include "io.hpp" #include "Packet.hpp" -#include "Server.hpp" #include "../app/init.hpp" #include "../model/CompositeModel.hpp" -#include "../world/ChunkIndex.hpp" #include "../world/Entity.hpp" #include "../world/EntityState.hpp" -#include "../world/World.hpp" #include -#include -#include using namespace std; @@ -34,413 +27,6 @@ constexpr size_t Packet::PlayerCorrection::MAX_LEN; constexpr size_t Packet::ChunkBegin::MAX_LEN; constexpr size_t Packet::ChunkData::MAX_LEN; -namespace { - -UDPsocket client_bind(Uint16 port) { - UDPsocket sock = SDLNet_UDP_Open(port); - if (!sock) { - throw NetError("SDLNet_UDP_Open"); - } - return sock; -} - -IPaddress client_resolve(const char *host, Uint16 port) { - IPaddress addr; - if (SDLNet_ResolveHost(&addr, host, port) != 0) { - throw NetError("SDLNet_ResolveHost"); - } - return addr; -} - -} - -Client::Client(const Config &conf) -: conn(client_resolve(conf.host.c_str(), conf.port)) -, client_sock(client_bind(0)) -, client_pack{ -1, nullptr, 0 } { - client_pack.data = new Uint8[sizeof(Packet)]; - client_pack.maxlen = sizeof(Packet); - // establish connection - SendPing(); -} - -Client::~Client() { - delete[] client_pack.data; - SDLNet_UDP_Close(client_sock); -} - - -void Client::Handle() { - int result = SDLNet_UDP_Recv(client_sock, &client_pack); - while (result > 0) { - HandlePacket(client_pack); - result = SDLNet_UDP_Recv(client_sock, &client_pack); - } - if (result == -1) { - // a boo boo happened - throw NetError("SDLNet_UDP_Recv"); - } -} - -void Client::HandlePacket(const UDPpacket &udp_pack) { - if (!conn.Matches(udp_pack.address)) { - // packet came from somewhere else, drop - return; - } - const Packet &pack = *reinterpret_cast(udp_pack.data); - if (pack.header.tag != Packet::TAG) { - // mistagged packet, drop - return; - } - - conn.Received(udp_pack); -} - -void Client::Update(int dt) { - conn.Update(dt); - if (conn.ShouldPing()) { - SendPing(); - } -} - -uint16_t Client::SendPing() { - return conn.SendPing(client_pack, client_sock); -} - -uint16_t Client::SendLogin(const string &name) { - auto pack = Packet::Make(client_pack); - pack.WritePlayerName(name); - return conn.Send(client_pack, client_sock); -} - -uint16_t Client::SendPlayerUpdate(const Entity &player) { - auto pack = Packet::Make(client_pack); - pack.WritePlayer(player); - return conn.Send(client_pack, client_sock); -} - -uint16_t Client::SendPart() { - Packet::Make(client_pack); - return conn.Send(client_pack, client_sock); -} - - -ClientConnection::ClientConnection(Server &server, const IPaddress &addr) -: server(server) -, conn(addr) -, player(nullptr, nullptr) -, spawns() -, confirm_wait(0) -, player_update_state() -, player_update_pack(0) -, player_update_timer(1500) -, transmitter(*this) -, chunk_queue() -, old_base() { - conn.SetHandler(this); -} - -ClientConnection::~ClientConnection() { - DetachPlayer(); -} - -void ClientConnection::Update(int dt) { - conn.Update(dt); - if (Disconnected()) { - return; - } - if (HasPlayer()) { - // sync entities - auto global_iter = server.GetWorld().Entities().begin(); - auto global_end = server.GetWorld().Entities().end(); - auto local_iter = spawns.begin(); - auto local_end = spawns.end(); - - while (global_iter != global_end && local_iter != local_end) { - if (global_iter->ID() == local_iter->entity->ID()) { - // they're the same - if (CanDespawn(*global_iter)) { - SendDespawn(*local_iter); - } else { - // update - SendUpdate(*local_iter); - } - ++global_iter; - ++local_iter; - } else if (global_iter->ID() < local_iter->entity->ID()) { - // global entity was inserted - if (CanSpawn(*global_iter)) { - auto spawned = spawns.emplace(local_iter, *global_iter); - SendSpawn(*spawned); - } - ++global_iter; - } else { - // global entity was removed - SendDespawn(*local_iter); - ++local_iter; - } - } - - // leftover spawns - while (global_iter != global_end) { - if (CanSpawn(*global_iter)) { - spawns.emplace_back(*global_iter); - SendSpawn(spawns.back()); - } - ++global_iter; - } - - // leftover despawns - while (local_iter != local_end) { - SendDespawn(*local_iter); - ++local_iter; - } - - CheckPlayerFix(); - CheckChunkQueue(); - } - if (conn.ShouldPing()) { - conn.SendPing(server.GetPacket(), server.GetSocket()); - } -} - -ClientConnection::SpawnStatus::SpawnStatus(Entity &e) -: entity(&e) -, spawn_pack(-1) -, despawn_pack(-1) { - entity->Ref(); -} - -ClientConnection::SpawnStatus::~SpawnStatus() { - entity->UnRef(); -} - -bool ClientConnection::CanSpawn(const Entity &e) const noexcept { - return - &e != player.entity && - !e.Dead() && - manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; -} - -bool ClientConnection::CanDespawn(const Entity &e) const noexcept { - return - e.Dead() || - manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) > 7; -} - -uint16_t ClientConnection::Send() { - return conn.Send(server.GetPacket(), server.GetSocket()); -} - -uint16_t ClientConnection::Send(size_t len) { - server.GetPacket().len = len; - return Send(); -} - -void ClientConnection::SendSpawn(SpawnStatus &status) { - // don't double spawn - if (status.spawn_pack != -1) return; - - auto pack = Prepare(); - pack.WriteEntity(*status.entity); - status.spawn_pack = Send(); - ++confirm_wait; -} - -void ClientConnection::SendDespawn(SpawnStatus &status) { - // don't double despawn - if (status.despawn_pack != -1) return; - - auto pack = Prepare(); - pack.WriteEntityID(status.entity->ID()); - status.despawn_pack = Send(); - ++confirm_wait; -} - -void ClientConnection::SendUpdate(SpawnStatus &status) { - // don't send updates while spawn not ack'd or despawn sent - if (status.spawn_pack != -1 || status.despawn_pack != -1) return; - - // TODO: pack entity updates - auto pack = Prepare(); - pack.WriteEntityCount(1); - pack.WriteEntity(*status.entity, 0); - Send(Packet::EntityUpdate::GetSize(1)); -} - -void ClientConnection::CheckPlayerFix() { - // player_update_state's position holds the client's most recent prediction - glm::vec3 diff = player_update_state.Diff(PlayerEntity().GetState()); - float dist_squared = dot(diff, diff); - - // if client's prediction is off by more than 1cm, send - // our (authoritative) state back so it can fix it - constexpr float fix_thresh = 0.0001f; - - if (dist_squared > fix_thresh) { - auto pack = Prepare(); - pack.WritePacketSeq(player_update_pack); - pack.WritePlayer(PlayerEntity()); - Send(); - } -} - -void ClientConnection::CheckChunkQueue() { - if (PlayerChunks().Base() != old_base) { - Chunk::Pos begin = PlayerChunks().CoordsBegin(); - Chunk::Pos end = PlayerChunks().CoordsEnd(); - for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { - for (pos.y = begin.y; pos.y < end.y; ++pos.y) { - for (pos.x = begin.x; pos.x < end.x; ++pos.x) { - if (manhattan_radius(pos - old_base) > PlayerChunks().Extent()) { - chunk_queue.push_back(pos); - } - } - } - } - old_base = PlayerChunks().Base(); - } - if (transmitter.Transmitting()) { - transmitter.Transmit(); - return; - } - if (transmitter.Idle()) { - int count = 0; - constexpr int max = 64; - while (count < max && !chunk_queue.empty()) { - Chunk::Pos pos = chunk_queue.front(); - chunk_queue.pop_front(); - if (PlayerChunks().InRange(pos)) { - Chunk *chunk = PlayerChunks().Get(pos); - if (chunk) { - transmitter.Send(*chunk); - return; - } else { - chunk_queue.push_back(pos); - } - ++count; - } - } - } -} - -void ClientConnection::AttachPlayer(const Player &new_player) { - DetachPlayer(); - player = new_player; - player.entity->Ref(); - - old_base = player.chunks->Base(); - Chunk::Pos begin = player.chunks->CoordsBegin(); - Chunk::Pos end = player.chunks->CoordsEnd(); - for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { - for (pos.y = begin.y; pos.y < end.y; ++pos.y) { - for (pos.x = begin.x; pos.x < end.x; ++pos.x) { - chunk_queue.push_back(pos); - } - } - } - - cout << "player \"" << player.entity->Name() << "\" joined" << endl; -} - -void ClientConnection::DetachPlayer() { - if (!HasPlayer()) return; - cout << "player \"" << player.entity->Name() << "\" left" << endl; - player.entity->Kill(); - player.entity->UnRef(); - player.entity = nullptr; - player.chunks = nullptr; - transmitter.Abort(); - chunk_queue.clear(); -} - -void ClientConnection::OnPacketReceived(uint16_t seq) { - if (transmitter.Waiting()) { - transmitter.Ack(seq); - } - if (!confirm_wait) return; - for (auto iter = spawns.begin(), end = spawns.end(); iter != end; ++iter) { - if (seq == iter->spawn_pack) { - iter->spawn_pack = -1; - --confirm_wait; - return; - } - if (seq == iter->despawn_pack) { - spawns.erase(iter); - --confirm_wait; - return; - } - } -} - -void ClientConnection::OnPacketLost(uint16_t seq) { - if (transmitter.Waiting()) { - transmitter.Nack(seq); - } - if (!confirm_wait) return; - for (SpawnStatus &status : spawns) { - if (seq == status.spawn_pack) { - status.spawn_pack = -1; - --confirm_wait; - SendSpawn(status); - return; - } - if (seq == status.despawn_pack) { - status.despawn_pack = -1; - --confirm_wait; - SendDespawn(status); - return; - } - } -} - -void ClientConnection::On(const Packet::Login &pack) { - string name; - pack.ReadPlayerName(name); - - Player new_player = server.GetWorld().AddPlayer(name); - - if (new_player.entity) { - // success! - AttachPlayer(new_player); - cout << "accepted login from player \"" << name << '"' << endl; - auto response = Prepare(); - response.WritePlayer(*new_player.entity); - response.WriteWorldName(server.GetWorld().Name()); - Send(); - // set up update tracking - player_update_state = new_player.entity->GetState(); - player_update_pack = pack.Seq(); - player_update_timer.Reset(); - player_update_timer.Start(); - } else { - // aw no :( - cout << "rejected login from player \"" << name << '"' << endl; - Prepare(); - Send(); - conn.Close(); - } -} - -void ClientConnection::On(const Packet::Part &) { - conn.Close(); -} - -void ClientConnection::On(const Packet::PlayerUpdate &pack) { - if (!HasPlayer()) return; - int pack_diff = int16_t(pack.Seq()) - int16_t(player_update_pack); - bool overdue = player_update_timer.HitOnce(); - player_update_timer.Reset(); - if (pack_diff > 0 || overdue) { - player_update_pack = pack.Seq(); - pack.ReadPlayerState(player_update_state); - // accept velocity and orientation as "user input" - PlayerEntity().Velocity(player_update_state.velocity); - PlayerEntity().Orientation(player_update_state.orient); - } -} - - Connection::Connection(const IPaddress &addr) : handler(nullptr) , addr(addr) @@ -876,73 +462,4 @@ void ConnectionHandler::Handle(const UDPpacket &udp_pack) { } } - -Server::Server(const Config &conf, World &world) -: serv_sock(nullptr) -, serv_pack{ -1, nullptr, 0 } -, clients() -, world(world) { - serv_sock = SDLNet_UDP_Open(conf.port); - if (!serv_sock) { - throw NetError("SDLNet_UDP_Open"); - } - - serv_pack.data = new Uint8[sizeof(Packet)]; - serv_pack.maxlen = sizeof(Packet); -} - -Server::~Server() { - delete[] serv_pack.data; - SDLNet_UDP_Close(serv_sock); -} - - -void Server::Handle() { - int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); - while (result > 0) { - HandlePacket(serv_pack); - result = SDLNet_UDP_Recv(serv_sock, &serv_pack); - } - if (result == -1) { - // a boo boo happened - throw NetError("SDLNet_UDP_Recv"); - } -} - -void Server::HandlePacket(const UDPpacket &udp_pack) { - if (udp_pack.len < int(sizeof(Packet::Header))) { - // packet too small, drop - return; - } - const Packet &pack = *reinterpret_cast(udp_pack.data); - if (pack.header.tag != Packet::TAG) { - // mistagged packet, drop - return; - } - - ClientConnection &client = GetClient(udp_pack.address); - client.GetConnection().Received(udp_pack); -} - -ClientConnection &Server::GetClient(const IPaddress &addr) { - for (ClientConnection &client : clients) { - if (client.Matches(addr)) { - return client; - } - } - clients.emplace_back(*this, addr); - return clients.back(); -} - -void Server::Update(int dt) { - for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { - client->Update(dt); - if (client->Disconnected()) { - client = clients.erase(client); - } else { - ++client; - } - } -} - } diff --git a/src/server/ChunkTransmitter.hpp b/src/server/ChunkTransmitter.hpp new file mode 100644 index 0000000..bb1c482 --- /dev/null +++ b/src/server/ChunkTransmitter.hpp @@ -0,0 +1,77 @@ +#ifndef BLANK_SERVER_CHUNKTRANSMITTER_HPP_ +#define BLANK_SERVER_CHUNKTRANSMITTER_HPP_ + +#include +#include +#include + + +namespace blank { + +class Chunk; + +namespace server { + +class ClientConnection; + +class ChunkTransmitter { + +public: + explicit ChunkTransmitter(ClientConnection &); + ~ChunkTransmitter(); + + /// Returns true if not transmitting or waiting on acks, so + /// the next chunk may be queued without schmutzing up anything. + bool Idle() const noexcept; + + /// Returns true if a transmission is still going on, + /// meaning there's at least one packet that needs to + /// be sent. + bool Transmitting() const noexcept; + /// Send the next packet of the current chunk (if any). + void Transmit(); + + /// Returns true if there's one or more packets which + /// still have to be ack'd by the remote. + bool Waiting() const noexcept; + /// Mark packet with given sequence number as ack'd. + /// If all packets for the current chunk have been ack'd + /// the transmission is considered complete. + void Ack(std::uint16_t); + /// Mark packet with given sequence number as lost. + /// Its part of the chunk data should be resent. + void Nack(std::uint16_t); + + /// Cancel the current transmission. + void Abort(); + /// Start transmitting given chunk. + /// If there's a chunk already in transmission it will be + /// cancelled. + void Send(Chunk &); + +private: + void SendBegin(); + void SendData(std::size_t); + void Release(); + +private: + ClientConnection &conn; + Chunk *current; + std::size_t buffer_size; + std::unique_ptr buffer; + std::size_t buffer_len; + std::size_t packet_len; + std::size_t cursor; + std::size_t num_packets; + int begin_packet; + std::vector data_packets; + int confirm_wait; + std::uint32_t trans_id; + bool compressed; + +}; + +} +} + +#endif diff --git a/src/server/ClientConnection.hpp b/src/server/ClientConnection.hpp new file mode 100644 index 0000000..c5efb51 --- /dev/null +++ b/src/server/ClientConnection.hpp @@ -0,0 +1,106 @@ +#ifndef BLANK_SERVER_CLIENTCONNECTION_HPP_ +#define BLANK_SERVER_CLIENTCONNECTION_HPP_ + +#include "ChunkTransmitter.hpp" +#include "Server.hpp" +#include "../app/IntervalTimer.hpp" +#include "../net/Connection.hpp" +#include "../net/ConnectionHandler.hpp" +#include "../world/EntityState.hpp" +#include "../world/Player.hpp" + +#include +#include +#include + + +namespace blank { +namespace server { + +class Server; + +class ClientConnection +: public ConnectionHandler { + +public: + explicit ClientConnection(Server &, const IPaddress &); + ~ClientConnection(); + + bool Matches(const IPaddress &addr) const noexcept { return conn.Matches(addr); } + + void Update(int dt); + + Connection &GetConnection() noexcept { return conn; } + bool Disconnected() const noexcept { return conn.Closed(); } + + /// prepare a packet of given type + template + Type Prepare() const noexcept { + return Packet::Make(server.GetPacket()); + } + /// send the previously prepared packet + std::uint16_t Send(); + /// send the previously prepared packet of non-default length + std::uint16_t Send(std::size_t len); + + void AttachPlayer(const Player &); + void DetachPlayer(); + bool HasPlayer() const noexcept { return player.entity; } + Entity &PlayerEntity() noexcept { return *player.entity; } + const Entity &PlayerEntity() const noexcept { return *player.entity; } + ChunkIndex &PlayerChunks() noexcept { return *player.chunks; } + const ChunkIndex &PlayerChunks() const noexcept { return *player.chunks; } + +private: + struct SpawnStatus { + // the entity in question + Entity *const entity = nullptr; + // sequence number of the spawn packet or -1 after it's been ack'd + std::int32_t spawn_pack = -1; + // sequence number of the despawn packet or -1 if no despawn has been sent + std::int32_t despawn_pack = -1; + + explicit SpawnStatus(Entity &); + ~SpawnStatus(); + }; + +private: + void OnPacketReceived(std::uint16_t) override; + void OnPacketLost(std::uint16_t) override; + + void On(const Packet::Login &) override; + void On(const Packet::Part &) override; + void On(const Packet::PlayerUpdate &) override; + + bool CanSpawn(const Entity &) const noexcept; + bool CanDespawn(const Entity &) const noexcept; + + void SendSpawn(SpawnStatus &); + void SendDespawn(SpawnStatus &); + void SendUpdate(SpawnStatus &); + + void CheckPlayerFix(); + + void CheckChunkQueue(); + +private: + Server &server; + Connection conn; + Player player; + std::list spawns; + unsigned int confirm_wait; + + EntityState player_update_state; + std::uint16_t player_update_pack; + IntervalTimer player_update_timer; + + ChunkTransmitter transmitter; + std::deque chunk_queue; + glm::ivec3 old_base; + +}; + +} +} + +#endif diff --git a/src/server/Server.hpp b/src/server/Server.hpp new file mode 100644 index 0000000..8bbb778 --- /dev/null +++ b/src/server/Server.hpp @@ -0,0 +1,53 @@ +#ifndef BLANK_SERVER_SERVER_HPP +#define BLANK_SERVER_SERVER_HPP + +#include +#include + + +namespace blank { + +class World; + +namespace server { + +class ClientConnection; + +class Server { + +public: + struct Config { + Uint16 port = 12354; + }; + +public: + Server(const Config &, World &); + ~Server(); + + void Handle(); + + void Update(int dt); + + UDPsocket &GetSocket() noexcept { return serv_sock; } + UDPpacket &GetPacket() noexcept { return serv_pack; } + + World &GetWorld() noexcept { return world; } + +private: + void HandlePacket(const UDPpacket &); + + ClientConnection &GetClient(const IPaddress &); + +private: + UDPsocket serv_sock; + UDPpacket serv_pack; + std::list clients; + + World &world; + +}; + +} +} + +#endif diff --git a/src/server/ServerState.cpp b/src/server/ServerState.cpp new file mode 100644 index 0000000..5545818 --- /dev/null +++ b/src/server/ServerState.cpp @@ -0,0 +1,68 @@ +#include "ServerState.hpp" + +#include "../app/Environment.hpp" +#include "../app/TextureIndex.hpp" +#include "../net/io.hpp" + +#include + + +namespace blank { +namespace server { + +ServerState::ServerState( + HeadlessEnvironment &env, + const Generator::Config &gc, + const World::Config &wc, + const WorldSave &ws, + const Server::Config &sc +) +: env(env) +, block_types() +, world(block_types, wc) +, generator(gc) +, chunk_loader(world.Chunks(), generator, ws) +, skeletons() +, spawner(world, skeletons, gc.seed) +, server(sc, world) +, loop_timer(16) { + TextureIndex tex_index; + env.loader.LoadBlockTypes("default", block_types, tex_index); + skeletons.LoadHeadless(); + + loop_timer.Start(); + + std::cout << "listening on UDP port " << sc.port << std::endl; +} + + +void ServerState::Handle(const SDL_Event &event) { + if (event.type == SDL_QUIT) { + env.state.PopAll(); + } +} + + +void ServerState::Update(int dt) { + loop_timer.Update(dt); + server.Handle(); + int world_dt = 0; + while (loop_timer.HitOnce()) { + spawner.Update(loop_timer.Interval()); + world.Update(loop_timer.Interval()); + world_dt += loop_timer.Interval(); + loop_timer.PopIteration(); + } + chunk_loader.Update(dt); + if (world_dt > 0) { + server.Update(world_dt); + } +} + + +void ServerState::Render(Viewport &viewport) { + +} + +} +} diff --git a/src/server/ServerState.hpp b/src/server/ServerState.hpp new file mode 100644 index 0000000..8f564ab --- /dev/null +++ b/src/server/ServerState.hpp @@ -0,0 +1,54 @@ +#ifndef BLANK_SERVER_SERVERSTATE_HPP_ +#define BLANK_SERVER_SERVERSTATE_HPP_ + +#include "Server.hpp" +#include "../ai/Spawner.hpp" +#include "../app/IntervalTimer.hpp" +#include "../app/State.hpp" +#include "../model/Skeletons.hpp" +#include "../world/BlockTypeRegistry.hpp" +#include "../world/ChunkLoader.hpp" +#include "../world/Generator.hpp" +#include "../world/World.hpp" + + +namespace blank { + +class HeadlessEnvironment; +class WorldSave; + +namespace server { + +class ServerState +: public State { + +public: + ServerState( + HeadlessEnvironment &, + const Generator::Config &, + const World::Config &, + const WorldSave &, + const Server::Config & + ); + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + +private: + HeadlessEnvironment &env; + BlockTypeRegistry block_types; + World world; + Generator generator; + ChunkLoader chunk_loader; + Skeletons skeletons; + Spawner spawner; + Server server; + IntervalTimer loop_timer; + +}; + +} +} + +#endif diff --git a/src/server/net.cpp b/src/server/net.cpp new file mode 100644 index 0000000..0cf0cdd --- /dev/null +++ b/src/server/net.cpp @@ -0,0 +1,556 @@ +#include "ClientConnection.hpp" +#include "ChunkTransmitter.hpp" +#include "Server.hpp" + +#include "../app/init.hpp" +#include "../world/ChunkIndex.hpp" +#include "../world/Entity.hpp" +#include "../world/World.hpp" + +#include +#include + +using namespace std; + + +namespace blank { +namespace server { + +ChunkTransmitter::ChunkTransmitter(ClientConnection &conn) +: conn(conn) +, current(nullptr) +, buffer_size(Chunk::BlockSize() + 10) +, buffer(new uint8_t[buffer_size]) +, buffer_len(0) +, packet_len(Packet::ChunkData::MAX_DATA_LEN) +, cursor(0) +, num_packets(0) +, begin_packet(-1) +, data_packets() +, confirm_wait(0) +, trans_id(0) +, compressed(false) { + +} + +ChunkTransmitter::~ChunkTransmitter() { + Abort(); +} + +bool ChunkTransmitter::Idle() const noexcept { + return !Transmitting() && !Waiting(); +} + +bool ChunkTransmitter::Transmitting() const noexcept { + return cursor < num_packets; +} + +void ChunkTransmitter::Transmit() { + if (cursor < num_packets) { + SendData(cursor); + ++cursor; + } +} + +bool ChunkTransmitter::Waiting() const noexcept { + return confirm_wait > 0; +} + +void ChunkTransmitter::Ack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + begin_packet = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + for (int i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + data_packets[i] = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + } +} + +void ChunkTransmitter::Nack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + SendBegin(); + return; + } + for (size_t i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + SendData(i); + return; + } + } +} + +void ChunkTransmitter::Abort() { + if (!current) return; + + Release(); + + begin_packet = -1; + data_packets.clear(); + confirm_wait = 0; +} + +void ChunkTransmitter::Send(Chunk &chunk) { + // abort current chunk, if any + Abort(); + + current = &chunk; + current->Ref(); + + // load new chunk data + compressed = true; + buffer_len = buffer_size; + if (compress(buffer.get(), &buffer_len, reinterpret_cast(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) { + // compression failed, send it uncompressed + buffer_len = Chunk::BlockSize(); + memcpy(buffer.get(), chunk.BlockData(), buffer_len); + compressed = false; + } + cursor = 0; + num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0); + data_packets.resize(num_packets, -1); + + ++trans_id; + SendBegin(); +} + +void ChunkTransmitter::SendBegin() { + uint32_t flags = compressed; + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteFlags(flags); + pack.WriteChunkCoords(current->Position()); + pack.WriteDataSize(buffer_len); + if (begin_packet == -1) { + ++confirm_wait; + } + begin_packet = conn.Send(); +} + +void ChunkTransmitter::SendData(size_t i) { + int pos = i * packet_len; + int len = min(packet_len, buffer_len - pos); + const uint8_t *data = &buffer[pos]; + + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteDataOffset(pos); + pack.WriteDataSize(len); + pack.WriteData(data, len); + + if (data_packets[i] == -1) { + ++confirm_wait; + } + data_packets[i] = conn.Send(); +} + +void ChunkTransmitter::Release() { + if (current) { + current->UnRef(); + current = nullptr; + } +} + + +ClientConnection::ClientConnection(Server &server, const IPaddress &addr) +: server(server) +, conn(addr) +, player(nullptr, nullptr) +, spawns() +, confirm_wait(0) +, player_update_state() +, player_update_pack(0) +, player_update_timer(1500) +, transmitter(*this) +, chunk_queue() +, old_base() { + conn.SetHandler(this); +} + +ClientConnection::~ClientConnection() { + DetachPlayer(); +} + +void ClientConnection::Update(int dt) { + conn.Update(dt); + if (Disconnected()) { + return; + } + if (HasPlayer()) { + // sync entities + auto global_iter = server.GetWorld().Entities().begin(); + auto global_end = server.GetWorld().Entities().end(); + auto local_iter = spawns.begin(); + auto local_end = spawns.end(); + + while (global_iter != global_end && local_iter != local_end) { + if (global_iter->ID() == local_iter->entity->ID()) { + // they're the same + if (CanDespawn(*global_iter)) { + SendDespawn(*local_iter); + } else { + // update + SendUpdate(*local_iter); + } + ++global_iter; + ++local_iter; + } else if (global_iter->ID() < local_iter->entity->ID()) { + // global entity was inserted + if (CanSpawn(*global_iter)) { + auto spawned = spawns.emplace(local_iter, *global_iter); + SendSpawn(*spawned); + } + ++global_iter; + } else { + // global entity was removed + SendDespawn(*local_iter); + ++local_iter; + } + } + + // leftover spawns + while (global_iter != global_end) { + if (CanSpawn(*global_iter)) { + spawns.emplace_back(*global_iter); + SendSpawn(spawns.back()); + } + ++global_iter; + } + + // leftover despawns + while (local_iter != local_end) { + SendDespawn(*local_iter); + ++local_iter; + } + + CheckPlayerFix(); + CheckChunkQueue(); + } + if (conn.ShouldPing()) { + conn.SendPing(server.GetPacket(), server.GetSocket()); + } +} + +ClientConnection::SpawnStatus::SpawnStatus(Entity &e) +: entity(&e) +, spawn_pack(-1) +, despawn_pack(-1) { + entity->Ref(); +} + +ClientConnection::SpawnStatus::~SpawnStatus() { + entity->UnRef(); +} + +bool ClientConnection::CanSpawn(const Entity &e) const noexcept { + return + &e != player.entity && + !e.Dead() && + manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; +} + +bool ClientConnection::CanDespawn(const Entity &e) const noexcept { + return + e.Dead() || + manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) > 7; +} + +uint16_t ClientConnection::Send() { + return conn.Send(server.GetPacket(), server.GetSocket()); +} + +uint16_t ClientConnection::Send(size_t len) { + server.GetPacket().len = len; + return Send(); +} + +void ClientConnection::SendSpawn(SpawnStatus &status) { + // don't double spawn + if (status.spawn_pack != -1) return; + + auto pack = Prepare(); + pack.WriteEntity(*status.entity); + status.spawn_pack = Send(); + ++confirm_wait; +} + +void ClientConnection::SendDespawn(SpawnStatus &status) { + // don't double despawn + if (status.despawn_pack != -1) return; + + auto pack = Prepare(); + pack.WriteEntityID(status.entity->ID()); + status.despawn_pack = Send(); + ++confirm_wait; +} + +void ClientConnection::SendUpdate(SpawnStatus &status) { + // don't send updates while spawn not ack'd or despawn sent + if (status.spawn_pack != -1 || status.despawn_pack != -1) return; + + // TODO: pack entity updates + auto pack = Prepare(); + pack.WriteEntityCount(1); + pack.WriteEntity(*status.entity, 0); + Send(Packet::EntityUpdate::GetSize(1)); +} + +void ClientConnection::CheckPlayerFix() { + // player_update_state's position holds the client's most recent prediction + glm::vec3 diff = player_update_state.Diff(PlayerEntity().GetState()); + float dist_squared = dot(diff, diff); + + // if client's prediction is off by more than 1cm, send + // our (authoritative) state back so it can fix it + constexpr float fix_thresh = 0.0001f; + + if (dist_squared > fix_thresh) { + auto pack = Prepare(); + pack.WritePacketSeq(player_update_pack); + pack.WritePlayer(PlayerEntity()); + Send(); + } +} + +void ClientConnection::CheckChunkQueue() { + if (PlayerChunks().Base() != old_base) { + Chunk::Pos begin = PlayerChunks().CoordsBegin(); + Chunk::Pos end = PlayerChunks().CoordsEnd(); + for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { + for (pos.y = begin.y; pos.y < end.y; ++pos.y) { + for (pos.x = begin.x; pos.x < end.x; ++pos.x) { + if (manhattan_radius(pos - old_base) > PlayerChunks().Extent()) { + chunk_queue.push_back(pos); + } + } + } + } + old_base = PlayerChunks().Base(); + } + if (transmitter.Transmitting()) { + transmitter.Transmit(); + return; + } + if (transmitter.Idle()) { + int count = 0; + constexpr int max = 64; + while (count < max && !chunk_queue.empty()) { + Chunk::Pos pos = chunk_queue.front(); + chunk_queue.pop_front(); + if (PlayerChunks().InRange(pos)) { + Chunk *chunk = PlayerChunks().Get(pos); + if (chunk) { + transmitter.Send(*chunk); + return; + } else { + chunk_queue.push_back(pos); + } + ++count; + } + } + } +} + +void ClientConnection::AttachPlayer(const Player &new_player) { + DetachPlayer(); + player = new_player; + player.entity->Ref(); + + old_base = player.chunks->Base(); + Chunk::Pos begin = player.chunks->CoordsBegin(); + Chunk::Pos end = player.chunks->CoordsEnd(); + for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { + for (pos.y = begin.y; pos.y < end.y; ++pos.y) { + for (pos.x = begin.x; pos.x < end.x; ++pos.x) { + chunk_queue.push_back(pos); + } + } + } + + cout << "player \"" << player.entity->Name() << "\" joined" << endl; +} + +void ClientConnection::DetachPlayer() { + if (!HasPlayer()) return; + cout << "player \"" << player.entity->Name() << "\" left" << endl; + player.entity->Kill(); + player.entity->UnRef(); + player.entity = nullptr; + player.chunks = nullptr; + transmitter.Abort(); + chunk_queue.clear(); +} + +void ClientConnection::OnPacketReceived(uint16_t seq) { + if (transmitter.Waiting()) { + transmitter.Ack(seq); + } + if (!confirm_wait) return; + for (auto iter = spawns.begin(), end = spawns.end(); iter != end; ++iter) { + if (seq == iter->spawn_pack) { + iter->spawn_pack = -1; + --confirm_wait; + return; + } + if (seq == iter->despawn_pack) { + spawns.erase(iter); + --confirm_wait; + return; + } + } +} + +void ClientConnection::OnPacketLost(uint16_t seq) { + if (transmitter.Waiting()) { + transmitter.Nack(seq); + } + if (!confirm_wait) return; + for (SpawnStatus &status : spawns) { + if (seq == status.spawn_pack) { + status.spawn_pack = -1; + --confirm_wait; + SendSpawn(status); + return; + } + if (seq == status.despawn_pack) { + status.despawn_pack = -1; + --confirm_wait; + SendDespawn(status); + return; + } + } +} + +void ClientConnection::On(const Packet::Login &pack) { + string name; + pack.ReadPlayerName(name); + + Player new_player = server.GetWorld().AddPlayer(name); + + if (new_player.entity) { + // success! + AttachPlayer(new_player); + cout << "accepted login from player \"" << name << '"' << endl; + auto response = Prepare(); + response.WritePlayer(*new_player.entity); + response.WriteWorldName(server.GetWorld().Name()); + Send(); + // set up update tracking + player_update_state = new_player.entity->GetState(); + player_update_pack = pack.Seq(); + player_update_timer.Reset(); + player_update_timer.Start(); + } else { + // aw no :( + cout << "rejected login from player \"" << name << '"' << endl; + Prepare(); + Send(); + conn.Close(); + } +} + +void ClientConnection::On(const Packet::Part &) { + conn.Close(); +} + +void ClientConnection::On(const Packet::PlayerUpdate &pack) { + if (!HasPlayer()) return; + int pack_diff = int16_t(pack.Seq()) - int16_t(player_update_pack); + bool overdue = player_update_timer.HitOnce(); + player_update_timer.Reset(); + if (pack_diff > 0 || overdue) { + player_update_pack = pack.Seq(); + pack.ReadPlayerState(player_update_state); + // accept velocity and orientation as "user input" + PlayerEntity().Velocity(player_update_state.velocity); + PlayerEntity().Orientation(player_update_state.orient); + } +} + + +Server::Server(const Config &conf, World &world) +: serv_sock(nullptr) +, serv_pack{ -1, nullptr, 0 } +, clients() +, world(world) { + serv_sock = SDLNet_UDP_Open(conf.port); + if (!serv_sock) { + throw NetError("SDLNet_UDP_Open"); + } + + serv_pack.data = new Uint8[sizeof(Packet)]; + serv_pack.maxlen = sizeof(Packet); +} + +Server::~Server() { + delete[] serv_pack.data; + SDLNet_UDP_Close(serv_sock); +} + + +void Server::Handle() { + int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + while (result > 0) { + HandlePacket(serv_pack); + result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); + } +} + +void Server::HandlePacket(const UDPpacket &udp_pack) { + if (udp_pack.len < int(sizeof(Packet::Header))) { + // packet too small, drop + return; + } + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop + return; + } + + ClientConnection &client = GetClient(udp_pack.address); + client.GetConnection().Received(udp_pack); +} + +ClientConnection &Server::GetClient(const IPaddress &addr) { + for (ClientConnection &client : clients) { + if (client.Matches(addr)) { + return client; + } + } + clients.emplace_back(*this, addr); + return clients.back(); +} + +void Server::Update(int dt) { + for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { + client->Update(dt); + if (client->Disconnected()) { + client = clients.erase(client); + } else { + ++client; + } + } +} + +} +}