#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"
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();
};
+++ /dev/null
-#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) {
-
-}
-
-}
+++ /dev/null
-#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
#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>
}
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);
}
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
#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"
#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>
--- /dev/null
+#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);
+}
+
+}
+}
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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;
- }
-}
-
-}
-#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;
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)
}
}
-
-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;
- }
- }
-}
-
}
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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) {
+
+}
+
+}
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+ }
+ }
+}
+
+}
+}