]> git.localhorst.tv Git - blank.git/commitdiff
move server and client stuff around
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 18 Sep 2015 13:01:21 +0000 (15:01 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 18 Sep 2015 13:01:21 +0000 (15:01 +0200)
24 files changed:
src/app/Runtime.hpp
src/app/ServerState.cpp [deleted file]
src/app/ServerState.hpp [deleted file]
src/app/runtime.cpp
src/client/ChunkReceiver.hpp [new file with mode: 0644]
src/client/ChunkTransmission.hpp [new file with mode: 0644]
src/client/Client.hpp [new file with mode: 0644]
src/client/InteractiveState.hpp
src/client/MasterState.hpp
src/client/net.cpp [new file with mode: 0644]
src/net/ChunkReceiver.hpp [deleted file]
src/net/ChunkTransmission.hpp [deleted file]
src/net/ChunkTransmitter.hpp [deleted file]
src/net/Client.hpp [deleted file]
src/net/ClientConnection.hpp [deleted file]
src/net/Server.hpp [deleted file]
src/net/chunk.cpp [deleted file]
src/net/net.cpp
src/server/ChunkTransmitter.hpp [new file with mode: 0644]
src/server/ClientConnection.hpp [new file with mode: 0644]
src/server/Server.hpp [new file with mode: 0644]
src/server/ServerState.cpp [new file with mode: 0644]
src/server/ServerState.hpp [new file with mode: 0644]
src/server/net.cpp [new file with mode: 0644]

index 65ad408d4ffffd7c1a826142f2a5a6b8734e95f5..36808a0e2a0e385cbcc9b2bd013e4310abdba1ef 100644 (file)
@@ -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 (file)
index 13abdc9..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "ServerState.hpp"
-
-#include "Environment.hpp"
-#include "TextureIndex.hpp"
-#include "../net/io.hpp"
-
-#include <iostream>
-
-
-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 (file)
index db21d0f..0000000
+++ /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
index 8ce7b5dd59562fa989487ae635cc266849939349..cc9d3bb189ca4b8f6a1032152c3d77008bf84e0c 100644 (file)
@@ -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 <cctype>
 #include <cstdlib>
@@ -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 (file)
index 0000000..74a03a7
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef BLANK_CLIENT_CHUNKRECEIVER_HPP_
+#define BLANK_CLIENT_CHUNKRECEIVER_HPP_
+
+#include "../app/IntervalTimer.hpp"
+#include "../net/Packet.hpp"
+
+#include <cstdint>
+#include <list>
+
+
+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<ChunkTransmission> transmissions;
+       IntervalTimer timer;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/client/ChunkTransmission.hpp b/src/client/ChunkTransmission.hpp
new file mode 100644 (file)
index 0000000..f54c7d8
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef BLANK_CLIENT_CHUNKTRANSMISSION_HPP_
+#define BLANK_CLIENT_CHUNKTRANSMISSION_HPP_
+
+#include "../world/Chunk.hpp"
+
+#include <cstdint>
+#include <glm/glm.hpp>
+
+
+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 (file)
index 0000000..68b2f7f
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef BLANK_CLIENT_CLIENT_HPP_
+#define BLANK_CLIENT_CLIENT_HPP_
+
+#include "../net/Connection.hpp"
+
+#include <string>
+#include <SDL_net.h>
+
+
+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
index 77e02c35365d9e2484a7ad7f0f652aa680dd167a..8cf9246a56d61a58c9be8fa5a04b4387fadd25b3 100644 (file)
@@ -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"
index ff1769fdecf6f4db622b3c5c6579be5222210c85..50773a63d03e7d700b7e7e5b7c63d6da8f8b33c3 100644 (file)
@@ -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 <map>
diff --git a/src/client/net.cpp b/src/client/net.cpp
new file mode 100644 (file)
index 0000000..ed2dd20
--- /dev/null
@@ -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 <iostream>
+#include <zlib.h>
+#include <glm/gtx/io.hpp>
+
+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<Byte *>(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<const Packet *>(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<Packet::Login>(client_pack);
+       pack.WritePlayerName(name);
+       return conn.Send(client_pack, client_sock);
+}
+
+uint16_t Client::SendPlayerUpdate(const Entity &player) {
+       auto pack = Packet::Make<Packet::PlayerUpdate>(client_pack);
+       pack.WritePlayer(player);
+       return conn.Send(client_pack, client_sock);
+}
+
+uint16_t Client::SendPart() {
+       Packet::Make<Packet::Part>(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 (file)
index 40acc2f..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef BLANK_NET_CHUNKRECEIVER_HPP_
-#define BLANK_NET_CHUNKRECEIVER_HPP_
-
-#include "Packet.hpp"
-#include "../app/IntervalTimer.hpp"
-
-#include <cstdint>
-#include <list>
-
-
-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<ChunkTransmission> transmissions;
-       IntervalTimer timer;
-
-};
-
-}
-
-#endif
diff --git a/src/net/ChunkTransmission.hpp b/src/net/ChunkTransmission.hpp
deleted file mode 100644 (file)
index 90dd35e..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_
-#define BLANK_NET_CHUNKTRANSMISSION_HPP_
-
-#include "../world/Chunk.hpp"
-
-#include <cstdint>
-#include <glm/glm.hpp>
-
-
-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 (file)
index 44f6bd8..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-#ifndef BLANK_NET_CHUNKTRANSMITTER_HPP_
-#define BLANK_NET_CHUNKTRANSMITTER_HPP_
-
-#include <cstdint>
-#include <memory>
-#include <vector>
-
-
-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<std::uint8_t[]> 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<int> 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 (file)
index 2848aed..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef BLANK_NET_CLIENT_HPP_
-#define BLANK_NET_CLIENT_HPP_
-
-#include "Connection.hpp"
-
-#include <string>
-#include <SDL_net.h>
-
-
-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 (file)
index 075fdce..0000000
+++ /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 <deque>
-#include <list>
-#include <SDL_net.h>
-
-
-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<class Type>
-       Type Prepare() const noexcept {
-               return Packet::Make<Type>(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<SpawnStatus> spawns;
-       unsigned int confirm_wait;
-
-       EntityState player_update_state;
-       std::uint16_t player_update_pack;
-       IntervalTimer player_update_timer;
-
-       ChunkTransmitter transmitter;
-       std::deque<glm::ivec3> chunk_queue;
-       glm::ivec3 old_base;
-
-};
-
-}
-
-#endif
diff --git a/src/net/Server.hpp b/src/net/Server.hpp
deleted file mode 100644 (file)
index a6d509b..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef BLANK_NET_SERVER_HPP
-#define BLANK_NET_SERVER_HPP
-
-#include <list>
-#include <SDL_net.h>
-
-
-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<ClientConnection> clients;
-
-       World &world;
-
-};
-
-}
-
-#endif
diff --git a/src/net/chunk.cpp b/src/net/chunk.cpp
deleted file mode 100644 (file)
index 96e5ce6..0000000
+++ /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 <iostream>
-#include <zlib.h>
-#include <glm/gtx/io.hpp>
-
-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<Byte *>(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<const Bytef *>(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<Packet::ChunkBegin>();
-       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<Packet::ChunkData>();
-       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;
-       }
-}
-
-}
index f59b80962530e5a4f4ca8b883b454247eb466a86..81de3e476e8286245e574905081b047d28b17308 100644 (file)
@@ -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 <cstring>
-#include <iostream>
-#include <glm/gtx/io.hpp>
 
 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<const Packet *>(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<Packet::Login>(client_pack);
-       pack.WritePlayerName(name);
-       return conn.Send(client_pack, client_sock);
-}
-
-uint16_t Client::SendPlayerUpdate(const Entity &player) {
-       auto pack = Packet::Make<Packet::PlayerUpdate>(client_pack);
-       pack.WritePlayer(player);
-       return conn.Send(client_pack, client_sock);
-}
-
-uint16_t Client::SendPart() {
-       Packet::Make<Packet::Part>(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<Packet::SpawnEntity>();
-       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<Packet::DespawnEntity>();
-       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<Packet::EntityUpdate>();
-       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<Packet::PlayerCorrection>();
-               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<Packet::Join>();
-               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<Packet::Part>();
-               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<const Packet *>(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<ClientConnection>::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 (file)
index 0000000..bb1c482
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef BLANK_SERVER_CHUNKTRANSMITTER_HPP_
+#define BLANK_SERVER_CHUNKTRANSMITTER_HPP_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+
+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<std::uint8_t[]> 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<int> 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 (file)
index 0000000..c5efb51
--- /dev/null
@@ -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 <deque>
+#include <list>
+#include <SDL_net.h>
+
+
+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<class Type>
+       Type Prepare() const noexcept {
+               return Packet::Make<Type>(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<SpawnStatus> spawns;
+       unsigned int confirm_wait;
+
+       EntityState player_update_state;
+       std::uint16_t player_update_pack;
+       IntervalTimer player_update_timer;
+
+       ChunkTransmitter transmitter;
+       std::deque<glm::ivec3> chunk_queue;
+       glm::ivec3 old_base;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/server/Server.hpp b/src/server/Server.hpp
new file mode 100644 (file)
index 0000000..8bbb778
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef BLANK_SERVER_SERVER_HPP
+#define BLANK_SERVER_SERVER_HPP
+
+#include <list>
+#include <SDL_net.h>
+
+
+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<ClientConnection> clients;
+
+       World &world;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/server/ServerState.cpp b/src/server/ServerState.cpp
new file mode 100644 (file)
index 0000000..5545818
--- /dev/null
@@ -0,0 +1,68 @@
+#include "ServerState.hpp"
+
+#include "../app/Environment.hpp"
+#include "../app/TextureIndex.hpp"
+#include "../net/io.hpp"
+
+#include <iostream>
+
+
+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 (file)
index 0000000..8f564ab
--- /dev/null
@@ -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 (file)
index 0000000..0cf0cdd
--- /dev/null
@@ -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 <iostream>
+#include <zlib.h>
+
+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<const Bytef *>(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<Packet::ChunkBegin>();
+       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<Packet::ChunkData>();
+       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<Packet::SpawnEntity>();
+       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<Packet::DespawnEntity>();
+       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<Packet::EntityUpdate>();
+       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<Packet::PlayerCorrection>();
+               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<Packet::Join>();
+               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<Packet::Part>();
+               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<const Packet *>(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<ClientConnection>::iterator client(clients.begin()), end(clients.end()); client != end;) {
+               client->Update(dt);
+               if (client->Disconnected()) {
+                       client = clients.erase(client);
+               } else {
+                       ++client;
+               }
+       }
+}
+
+}
+}