From 8ae45b6555d55f301f83daf8c1337d332d8305ab Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 18 Sep 2015 15:01:21 +0200 Subject: [PATCH] move server and client stuff around --- src/app/Runtime.hpp | 8 +- src/app/runtime.cpp | 4 +- src/{net => client}/ChunkReceiver.hpp | 10 +- src/{net => client}/ChunkTransmission.hpp | 8 +- src/{net => client}/Client.hpp | 9 +- src/client/InteractiveState.hpp | 2 +- src/client/MasterState.hpp | 2 +- src/{net/chunk.cpp => client/net.cpp} | 184 +++---- src/net/net.cpp | 483 ------------------- src/{net => server}/ChunkTransmitter.hpp | 8 +- src/{net => server}/ClientConnection.hpp | 10 +- src/{net => server}/Server.hpp | 10 +- src/{app => server}/ServerState.cpp | 6 +- src/{app => server}/ServerState.hpp | 13 +- src/server/net.cpp | 556 ++++++++++++++++++++++ 15 files changed, 676 insertions(+), 637 deletions(-) rename src/{net => client}/ChunkReceiver.hpp (80%) rename src/{net => client}/ChunkTransmission.hpp (81%) rename src/{net => client}/Client.hpp (85%) rename src/{net/chunk.cpp => client/net.cpp} (54%) rename src/{net => server}/ChunkTransmitter.hpp (93%) rename src/{net => server}/ClientConnection.hpp (93%) rename src/{net => server}/Server.hpp (88%) rename src/{app => server}/ServerState.cpp (92%) rename src/{app => server}/ServerState.hpp (81%) create mode 100644 src/server/net.cpp diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp index 65ad408..36808a0 100644 --- a/src/app/Runtime.hpp +++ b/src/app/Runtime.hpp @@ -2,8 +2,8 @@ #define BLANK_RUNTIME_HPP_ #include "Environment.hpp" -#include "../net/Client.hpp" -#include "../net/Server.hpp" +#include "../client/Client.hpp" +#include "../server/Server.hpp" #include "../ui/Interface.hpp" #include "../world/Generator.hpp" #include "../world/World.hpp" @@ -44,11 +44,11 @@ public: bool doublebuf = true; int multisampling = 1; - Client::Config client = Client::Config(); + client::Client::Config client = client::Client::Config(); Generator::Config gen = Generator::Config(); HeadlessEnvironment::Config env = HeadlessEnvironment::Config(); Interface::Config interface = Interface::Config(); - Server::Config server = Server::Config(); + server::Server::Config server = server::Server::Config(); World::Config world = World::Config(); }; diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index 8ce7b5d..cc9d3bb 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -1,13 +1,13 @@ #include "Application.hpp" #include "Environment.hpp" #include "Runtime.hpp" -#include "ServerState.hpp" #include "WorldState.hpp" #include "init.hpp" #include "../client/MasterState.hpp" #include "../io/filesystem.hpp" #include "../io/WorldSave.hpp" +#include "../server/ServerState.hpp" #include #include @@ -342,7 +342,7 @@ void Runtime::RunServer() { } HeadlessApplication app(env); - ServerState server_state(env, config.gen, config.world, save, config.server); + server::ServerState server_state(env, config.gen, config.world, save, config.server); app.PushState(&server_state); Run(app); } diff --git a/src/net/ChunkReceiver.hpp b/src/client/ChunkReceiver.hpp similarity index 80% rename from src/net/ChunkReceiver.hpp rename to src/client/ChunkReceiver.hpp index 40acc2f..74a03a7 100644 --- a/src/net/ChunkReceiver.hpp +++ b/src/client/ChunkReceiver.hpp @@ -1,8 +1,8 @@ -#ifndef BLANK_NET_CHUNKRECEIVER_HPP_ -#define BLANK_NET_CHUNKRECEIVER_HPP_ +#ifndef BLANK_CLIENT_CHUNKRECEIVER_HPP_ +#define BLANK_CLIENT_CHUNKRECEIVER_HPP_ -#include "Packet.hpp" #include "../app/IntervalTimer.hpp" +#include "../net/Packet.hpp" #include #include @@ -11,6 +11,9 @@ namespace blank { class ChunkStore; + +namespace client { + class ChunkTransmission; class ChunkReceiver { @@ -35,6 +38,7 @@ private: }; +} } #endif diff --git a/src/net/ChunkTransmission.hpp b/src/client/ChunkTransmission.hpp similarity index 81% rename from src/net/ChunkTransmission.hpp rename to src/client/ChunkTransmission.hpp index 90dd35e..f54c7d8 100644 --- a/src/net/ChunkTransmission.hpp +++ b/src/client/ChunkTransmission.hpp @@ -1,5 +1,5 @@ -#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_ -#define BLANK_NET_CHUNKTRANSMISSION_HPP_ +#ifndef BLANK_CLIENT_CHUNKTRANSMISSION_HPP_ +#define BLANK_CLIENT_CHUNKTRANSMISSION_HPP_ #include "../world/Chunk.hpp" @@ -8,6 +8,7 @@ namespace blank { +namespace client { struct ChunkTransmission { @@ -35,6 +36,7 @@ struct ChunkTransmission { }; -}; +} +} #endif diff --git a/src/net/Client.hpp b/src/client/Client.hpp similarity index 85% rename from src/net/Client.hpp rename to src/client/Client.hpp index 2848aed..68b2f7f 100644 --- a/src/net/Client.hpp +++ b/src/client/Client.hpp @@ -1,7 +1,7 @@ -#ifndef BLANK_NET_CLIENT_HPP_ -#define BLANK_NET_CLIENT_HPP_ +#ifndef BLANK_CLIENT_CLIENT_HPP_ +#define BLANK_CLIENT_CLIENT_HPP_ -#include "Connection.hpp" +#include "../net/Connection.hpp" #include #include @@ -11,6 +11,8 @@ namespace blank { class World; +namespace client { + class Client { public: @@ -45,6 +47,7 @@ private: }; +} } #endif diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index 77e02c3..8cf9246 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -1,12 +1,12 @@ #ifndef BLANK_CLIENT_INTERACTIVESTATE_HPP_ #define BLANK_CLIENT_INTERACTIVESTATE_HPP_ +#include "ChunkReceiver.hpp" #include "ChunkRequester.hpp" #include "../app/IntervalTimer.hpp" #include "../app/State.hpp" #include "../io/WorldSave.hpp" #include "../model/Skeletons.hpp" -#include "../net/ChunkReceiver.hpp" #include "../ui/Interface.hpp" #include "../world/BlockTypeRegistry.hpp" #include "../world/ChunkRenderer.hpp" diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp index ff1769f..50773a6 100644 --- a/src/client/MasterState.hpp +++ b/src/client/MasterState.hpp @@ -1,10 +1,10 @@ #ifndef BLANK_CLIENT_CLIENTSTATE_HPP_ #define BLANK_CLIENT_CLIENTSTATE_HPP_ +#include "Client.hpp" #include "InitialState.hpp" #include "InteractiveState.hpp" #include "../app/State.hpp" -#include "../net/Client.hpp" #include "../net/ConnectionHandler.hpp" #include diff --git a/src/net/chunk.cpp b/src/client/net.cpp similarity index 54% rename from src/net/chunk.cpp rename to src/client/net.cpp index 96e5ce6..ed2dd20 100644 --- a/src/net/chunk.cpp +++ b/src/client/net.cpp @@ -1,9 +1,9 @@ #include "ChunkReceiver.hpp" #include "ChunkTransmission.hpp" -#include "ChunkTransmitter.hpp" +#include "Client.hpp" -#include "ClientConnection.hpp" -#include "Packet.hpp" +#include "../app/init.hpp" +#include "../net/Packet.hpp" #include "../world/Chunk.hpp" #include "../world/ChunkStore.hpp" @@ -15,6 +15,8 @@ using namespace std; namespace blank { +namespace client { + ChunkReceiver::ChunkReceiver(ChunkStore &store) : store(store) @@ -154,155 +156,95 @@ bool ChunkTransmission::Compressed() const noexcept { } -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) { +namespace { +UDPsocket client_bind(Uint16 port) { + UDPsocket sock = SDLNet_UDP_Open(port); + if (!sock) { + throw NetError("SDLNet_UDP_Open"); + } + return sock; } -ChunkTransmitter::~ChunkTransmitter() { - Abort(); +IPaddress client_resolve(const char *host, Uint16 port) { + IPaddress addr; + if (SDLNet_ResolveHost(&addr, host, port) != 0) { + throw NetError("SDLNet_ResolveHost"); + } + return addr; } -bool ChunkTransmitter::Idle() const noexcept { - return !Transmitting() && !Waiting(); } -bool ChunkTransmitter::Transmitting() const noexcept { - return cursor < num_packets; +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(); } -void ChunkTransmitter::Transmit() { - if (cursor < num_packets) { - SendData(cursor); - ++cursor; - } +Client::~Client() { + delete[] client_pack.data; + SDLNet_UDP_Close(client_sock); } -bool ChunkTransmitter::Waiting() const noexcept { - return confirm_wait > 0; -} -void ChunkTransmitter::Ack(uint16_t seq) { - if (!Waiting()) { - return; +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 (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; - } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); } } -void ChunkTransmitter::Nack(uint16_t seq) { - if (!Waiting()) { +void Client::HandlePacket(const UDPpacket &udp_pack) { + if (!conn.Matches(udp_pack.address)) { + // packet came from somewhere else, drop return; } - if (seq == begin_packet) { - SendBegin(); + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop 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; + conn.Received(udp_pack); } -void ChunkTransmitter::Send(Chunk &chunk) { - // abort current chunk, if any - Abort(); - - current = &chunk; - current->Ref(); - - // load new chunk data - compressed = true; - buffer_len = buffer_size; - if (compress(buffer.get(), &buffer_len, reinterpret_cast(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) { - // compression failed, send it uncompressed - buffer_len = Chunk::BlockSize(); - memcpy(buffer.get(), chunk.BlockData(), buffer_len); - compressed = false; +void Client::Update(int dt) { + conn.Update(dt); + if (conn.ShouldPing()) { + SendPing(); } - cursor = 0; - num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0); - data_packets.resize(num_packets, -1); - - ++trans_id; - SendBegin(); } -void ChunkTransmitter::SendBegin() { - uint32_t flags = compressed; - auto pack = conn.Prepare(); - pack.WriteTransmissionId(trans_id); - pack.WriteFlags(flags); - pack.WriteChunkCoords(current->Position()); - pack.WriteDataSize(buffer_len); - if (begin_packet == -1) { - ++confirm_wait; - } - begin_packet = conn.Send(); +uint16_t Client::SendPing() { + return conn.SendPing(client_pack, client_sock); } -void ChunkTransmitter::SendData(size_t i) { - int pos = i * packet_len; - int len = min(packet_len, buffer_len - pos); - const uint8_t *data = &buffer[pos]; - - auto pack = conn.Prepare(); - pack.WriteTransmissionId(trans_id); - pack.WriteDataOffset(pos); - pack.WriteDataSize(len); - pack.WriteData(data, len); +uint16_t Client::SendLogin(const string &name) { + auto pack = Packet::Make(client_pack); + pack.WritePlayerName(name); + return conn.Send(client_pack, client_sock); +} - if (data_packets[i] == -1) { - ++confirm_wait; - } - data_packets[i] = conn.Send(); +uint16_t Client::SendPlayerUpdate(const Entity &player) { + auto pack = Packet::Make(client_pack); + pack.WritePlayer(player); + return conn.Send(client_pack, client_sock); } -void ChunkTransmitter::Release() { - if (current) { - current->UnRef(); - current = nullptr; - } +uint16_t Client::SendPart() { + Packet::Make(client_pack); + return conn.Send(client_pack, client_sock); } } +} diff --git a/src/net/net.cpp b/src/net/net.cpp index f59b809..81de3e4 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -1,21 +1,14 @@ -#include "Client.hpp" -#include "ClientConnection.hpp" #include "Connection.hpp" #include "ConnectionHandler.hpp" #include "io.hpp" #include "Packet.hpp" -#include "Server.hpp" #include "../app/init.hpp" #include "../model/CompositeModel.hpp" -#include "../world/ChunkIndex.hpp" #include "../world/Entity.hpp" #include "../world/EntityState.hpp" -#include "../world/World.hpp" #include -#include -#include using namespace std; @@ -34,413 +27,6 @@ constexpr size_t Packet::PlayerCorrection::MAX_LEN; constexpr size_t Packet::ChunkBegin::MAX_LEN; constexpr size_t Packet::ChunkData::MAX_LEN; -namespace { - -UDPsocket client_bind(Uint16 port) { - UDPsocket sock = SDLNet_UDP_Open(port); - if (!sock) { - throw NetError("SDLNet_UDP_Open"); - } - return sock; -} - -IPaddress client_resolve(const char *host, Uint16 port) { - IPaddress addr; - if (SDLNet_ResolveHost(&addr, host, port) != 0) { - throw NetError("SDLNet_ResolveHost"); - } - return addr; -} - -} - -Client::Client(const Config &conf) -: conn(client_resolve(conf.host.c_str(), conf.port)) -, client_sock(client_bind(0)) -, client_pack{ -1, nullptr, 0 } { - client_pack.data = new Uint8[sizeof(Packet)]; - client_pack.maxlen = sizeof(Packet); - // establish connection - SendPing(); -} - -Client::~Client() { - delete[] client_pack.data; - SDLNet_UDP_Close(client_sock); -} - - -void Client::Handle() { - int result = SDLNet_UDP_Recv(client_sock, &client_pack); - while (result > 0) { - HandlePacket(client_pack); - result = SDLNet_UDP_Recv(client_sock, &client_pack); - } - if (result == -1) { - // a boo boo happened - throw NetError("SDLNet_UDP_Recv"); - } -} - -void Client::HandlePacket(const UDPpacket &udp_pack) { - if (!conn.Matches(udp_pack.address)) { - // packet came from somewhere else, drop - return; - } - const Packet &pack = *reinterpret_cast(udp_pack.data); - if (pack.header.tag != Packet::TAG) { - // mistagged packet, drop - return; - } - - conn.Received(udp_pack); -} - -void Client::Update(int dt) { - conn.Update(dt); - if (conn.ShouldPing()) { - SendPing(); - } -} - -uint16_t Client::SendPing() { - return conn.SendPing(client_pack, client_sock); -} - -uint16_t Client::SendLogin(const string &name) { - auto pack = Packet::Make(client_pack); - pack.WritePlayerName(name); - return conn.Send(client_pack, client_sock); -} - -uint16_t Client::SendPlayerUpdate(const Entity &player) { - auto pack = Packet::Make(client_pack); - pack.WritePlayer(player); - return conn.Send(client_pack, client_sock); -} - -uint16_t Client::SendPart() { - Packet::Make(client_pack); - return conn.Send(client_pack, client_sock); -} - - -ClientConnection::ClientConnection(Server &server, const IPaddress &addr) -: server(server) -, conn(addr) -, player(nullptr, nullptr) -, spawns() -, confirm_wait(0) -, player_update_state() -, player_update_pack(0) -, player_update_timer(1500) -, transmitter(*this) -, chunk_queue() -, old_base() { - conn.SetHandler(this); -} - -ClientConnection::~ClientConnection() { - DetachPlayer(); -} - -void ClientConnection::Update(int dt) { - conn.Update(dt); - if (Disconnected()) { - return; - } - if (HasPlayer()) { - // sync entities - auto global_iter = server.GetWorld().Entities().begin(); - auto global_end = server.GetWorld().Entities().end(); - auto local_iter = spawns.begin(); - auto local_end = spawns.end(); - - while (global_iter != global_end && local_iter != local_end) { - if (global_iter->ID() == local_iter->entity->ID()) { - // they're the same - if (CanDespawn(*global_iter)) { - SendDespawn(*local_iter); - } else { - // update - SendUpdate(*local_iter); - } - ++global_iter; - ++local_iter; - } else if (global_iter->ID() < local_iter->entity->ID()) { - // global entity was inserted - if (CanSpawn(*global_iter)) { - auto spawned = spawns.emplace(local_iter, *global_iter); - SendSpawn(*spawned); - } - ++global_iter; - } else { - // global entity was removed - SendDespawn(*local_iter); - ++local_iter; - } - } - - // leftover spawns - while (global_iter != global_end) { - if (CanSpawn(*global_iter)) { - spawns.emplace_back(*global_iter); - SendSpawn(spawns.back()); - } - ++global_iter; - } - - // leftover despawns - while (local_iter != local_end) { - SendDespawn(*local_iter); - ++local_iter; - } - - CheckPlayerFix(); - CheckChunkQueue(); - } - if (conn.ShouldPing()) { - conn.SendPing(server.GetPacket(), server.GetSocket()); - } -} - -ClientConnection::SpawnStatus::SpawnStatus(Entity &e) -: entity(&e) -, spawn_pack(-1) -, despawn_pack(-1) { - entity->Ref(); -} - -ClientConnection::SpawnStatus::~SpawnStatus() { - entity->UnRef(); -} - -bool ClientConnection::CanSpawn(const Entity &e) const noexcept { - return - &e != player.entity && - !e.Dead() && - manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; -} - -bool ClientConnection::CanDespawn(const Entity &e) const noexcept { - return - e.Dead() || - manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) > 7; -} - -uint16_t ClientConnection::Send() { - return conn.Send(server.GetPacket(), server.GetSocket()); -} - -uint16_t ClientConnection::Send(size_t len) { - server.GetPacket().len = len; - return Send(); -} - -void ClientConnection::SendSpawn(SpawnStatus &status) { - // don't double spawn - if (status.spawn_pack != -1) return; - - auto pack = Prepare(); - pack.WriteEntity(*status.entity); - status.spawn_pack = Send(); - ++confirm_wait; -} - -void ClientConnection::SendDespawn(SpawnStatus &status) { - // don't double despawn - if (status.despawn_pack != -1) return; - - auto pack = Prepare(); - pack.WriteEntityID(status.entity->ID()); - status.despawn_pack = Send(); - ++confirm_wait; -} - -void ClientConnection::SendUpdate(SpawnStatus &status) { - // don't send updates while spawn not ack'd or despawn sent - if (status.spawn_pack != -1 || status.despawn_pack != -1) return; - - // TODO: pack entity updates - auto pack = Prepare(); - pack.WriteEntityCount(1); - pack.WriteEntity(*status.entity, 0); - Send(Packet::EntityUpdate::GetSize(1)); -} - -void ClientConnection::CheckPlayerFix() { - // player_update_state's position holds the client's most recent prediction - glm::vec3 diff = player_update_state.Diff(PlayerEntity().GetState()); - float dist_squared = dot(diff, diff); - - // if client's prediction is off by more than 1cm, send - // our (authoritative) state back so it can fix it - constexpr float fix_thresh = 0.0001f; - - if (dist_squared > fix_thresh) { - auto pack = Prepare(); - pack.WritePacketSeq(player_update_pack); - pack.WritePlayer(PlayerEntity()); - Send(); - } -} - -void ClientConnection::CheckChunkQueue() { - if (PlayerChunks().Base() != old_base) { - Chunk::Pos begin = PlayerChunks().CoordsBegin(); - Chunk::Pos end = PlayerChunks().CoordsEnd(); - for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { - for (pos.y = begin.y; pos.y < end.y; ++pos.y) { - for (pos.x = begin.x; pos.x < end.x; ++pos.x) { - if (manhattan_radius(pos - old_base) > PlayerChunks().Extent()) { - chunk_queue.push_back(pos); - } - } - } - } - old_base = PlayerChunks().Base(); - } - if (transmitter.Transmitting()) { - transmitter.Transmit(); - return; - } - if (transmitter.Idle()) { - int count = 0; - constexpr int max = 64; - while (count < max && !chunk_queue.empty()) { - Chunk::Pos pos = chunk_queue.front(); - chunk_queue.pop_front(); - if (PlayerChunks().InRange(pos)) { - Chunk *chunk = PlayerChunks().Get(pos); - if (chunk) { - transmitter.Send(*chunk); - return; - } else { - chunk_queue.push_back(pos); - } - ++count; - } - } - } -} - -void ClientConnection::AttachPlayer(const Player &new_player) { - DetachPlayer(); - player = new_player; - player.entity->Ref(); - - old_base = player.chunks->Base(); - Chunk::Pos begin = player.chunks->CoordsBegin(); - Chunk::Pos end = player.chunks->CoordsEnd(); - for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { - for (pos.y = begin.y; pos.y < end.y; ++pos.y) { - for (pos.x = begin.x; pos.x < end.x; ++pos.x) { - chunk_queue.push_back(pos); - } - } - } - - cout << "player \"" << player.entity->Name() << "\" joined" << endl; -} - -void ClientConnection::DetachPlayer() { - if (!HasPlayer()) return; - cout << "player \"" << player.entity->Name() << "\" left" << endl; - player.entity->Kill(); - player.entity->UnRef(); - player.entity = nullptr; - player.chunks = nullptr; - transmitter.Abort(); - chunk_queue.clear(); -} - -void ClientConnection::OnPacketReceived(uint16_t seq) { - if (transmitter.Waiting()) { - transmitter.Ack(seq); - } - if (!confirm_wait) return; - for (auto iter = spawns.begin(), end = spawns.end(); iter != end; ++iter) { - if (seq == iter->spawn_pack) { - iter->spawn_pack = -1; - --confirm_wait; - return; - } - if (seq == iter->despawn_pack) { - spawns.erase(iter); - --confirm_wait; - return; - } - } -} - -void ClientConnection::OnPacketLost(uint16_t seq) { - if (transmitter.Waiting()) { - transmitter.Nack(seq); - } - if (!confirm_wait) return; - for (SpawnStatus &status : spawns) { - if (seq == status.spawn_pack) { - status.spawn_pack = -1; - --confirm_wait; - SendSpawn(status); - return; - } - if (seq == status.despawn_pack) { - status.despawn_pack = -1; - --confirm_wait; - SendDespawn(status); - return; - } - } -} - -void ClientConnection::On(const Packet::Login &pack) { - string name; - pack.ReadPlayerName(name); - - Player new_player = server.GetWorld().AddPlayer(name); - - if (new_player.entity) { - // success! - AttachPlayer(new_player); - cout << "accepted login from player \"" << name << '"' << endl; - auto response = Prepare(); - response.WritePlayer(*new_player.entity); - response.WriteWorldName(server.GetWorld().Name()); - Send(); - // set up update tracking - player_update_state = new_player.entity->GetState(); - player_update_pack = pack.Seq(); - player_update_timer.Reset(); - player_update_timer.Start(); - } else { - // aw no :( - cout << "rejected login from player \"" << name << '"' << endl; - Prepare(); - Send(); - conn.Close(); - } -} - -void ClientConnection::On(const Packet::Part &) { - conn.Close(); -} - -void ClientConnection::On(const Packet::PlayerUpdate &pack) { - if (!HasPlayer()) return; - int pack_diff = int16_t(pack.Seq()) - int16_t(player_update_pack); - bool overdue = player_update_timer.HitOnce(); - player_update_timer.Reset(); - if (pack_diff > 0 || overdue) { - player_update_pack = pack.Seq(); - pack.ReadPlayerState(player_update_state); - // accept velocity and orientation as "user input" - PlayerEntity().Velocity(player_update_state.velocity); - PlayerEntity().Orientation(player_update_state.orient); - } -} - - Connection::Connection(const IPaddress &addr) : handler(nullptr) , addr(addr) @@ -876,73 +462,4 @@ void ConnectionHandler::Handle(const UDPpacket &udp_pack) { } } - -Server::Server(const Config &conf, World &world) -: serv_sock(nullptr) -, serv_pack{ -1, nullptr, 0 } -, clients() -, world(world) { - serv_sock = SDLNet_UDP_Open(conf.port); - if (!serv_sock) { - throw NetError("SDLNet_UDP_Open"); - } - - serv_pack.data = new Uint8[sizeof(Packet)]; - serv_pack.maxlen = sizeof(Packet); -} - -Server::~Server() { - delete[] serv_pack.data; - SDLNet_UDP_Close(serv_sock); -} - - -void Server::Handle() { - int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); - while (result > 0) { - HandlePacket(serv_pack); - result = SDLNet_UDP_Recv(serv_sock, &serv_pack); - } - if (result == -1) { - // a boo boo happened - throw NetError("SDLNet_UDP_Recv"); - } -} - -void Server::HandlePacket(const UDPpacket &udp_pack) { - if (udp_pack.len < int(sizeof(Packet::Header))) { - // packet too small, drop - return; - } - const Packet &pack = *reinterpret_cast(udp_pack.data); - if (pack.header.tag != Packet::TAG) { - // mistagged packet, drop - return; - } - - ClientConnection &client = GetClient(udp_pack.address); - client.GetConnection().Received(udp_pack); -} - -ClientConnection &Server::GetClient(const IPaddress &addr) { - for (ClientConnection &client : clients) { - if (client.Matches(addr)) { - return client; - } - } - clients.emplace_back(*this, addr); - return clients.back(); -} - -void Server::Update(int dt) { - for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { - client->Update(dt); - if (client->Disconnected()) { - client = clients.erase(client); - } else { - ++client; - } - } -} - } diff --git a/src/net/ChunkTransmitter.hpp b/src/server/ChunkTransmitter.hpp similarity index 93% rename from src/net/ChunkTransmitter.hpp rename to src/server/ChunkTransmitter.hpp index 44f6bd8..bb1c482 100644 --- a/src/net/ChunkTransmitter.hpp +++ b/src/server/ChunkTransmitter.hpp @@ -1,5 +1,5 @@ -#ifndef BLANK_NET_CHUNKTRANSMITTER_HPP_ -#define BLANK_NET_CHUNKTRANSMITTER_HPP_ +#ifndef BLANK_SERVER_CHUNKTRANSMITTER_HPP_ +#define BLANK_SERVER_CHUNKTRANSMITTER_HPP_ #include #include @@ -9,6 +9,9 @@ namespace blank { class Chunk; + +namespace server { + class ClientConnection; class ChunkTransmitter { @@ -68,6 +71,7 @@ private: }; +} } #endif diff --git a/src/net/ClientConnection.hpp b/src/server/ClientConnection.hpp similarity index 93% rename from src/net/ClientConnection.hpp rename to src/server/ClientConnection.hpp index 075fdce..c5efb51 100644 --- a/src/net/ClientConnection.hpp +++ b/src/server/ClientConnection.hpp @@ -1,11 +1,11 @@ -#ifndef BLANK_NET_CLIENTCONNECTION_HPP_ -#define BLANK_NET_CLIENTCONNECTION_HPP_ +#ifndef BLANK_SERVER_CLIENTCONNECTION_HPP_ +#define BLANK_SERVER_CLIENTCONNECTION_HPP_ #include "ChunkTransmitter.hpp" -#include "Connection.hpp" -#include "ConnectionHandler.hpp" #include "Server.hpp" #include "../app/IntervalTimer.hpp" +#include "../net/Connection.hpp" +#include "../net/ConnectionHandler.hpp" #include "../world/EntityState.hpp" #include "../world/Player.hpp" @@ -15,6 +15,7 @@ namespace blank { +namespace server { class Server; @@ -99,6 +100,7 @@ private: }; +} } #endif diff --git a/src/net/Server.hpp b/src/server/Server.hpp similarity index 88% rename from src/net/Server.hpp rename to src/server/Server.hpp index a6d509b..8bbb778 100644 --- a/src/net/Server.hpp +++ b/src/server/Server.hpp @@ -1,5 +1,5 @@ -#ifndef BLANK_NET_SERVER_HPP -#define BLANK_NET_SERVER_HPP +#ifndef BLANK_SERVER_SERVER_HPP +#define BLANK_SERVER_SERVER_HPP #include #include @@ -7,9 +7,12 @@ namespace blank { -class ClientConnection; class World; +namespace server { + +class ClientConnection; + class Server { public: @@ -44,6 +47,7 @@ private: }; +} } #endif diff --git a/src/app/ServerState.cpp b/src/server/ServerState.cpp similarity index 92% rename from src/app/ServerState.cpp rename to src/server/ServerState.cpp index 13abdc9..5545818 100644 --- a/src/app/ServerState.cpp +++ b/src/server/ServerState.cpp @@ -1,13 +1,14 @@ #include "ServerState.hpp" -#include "Environment.hpp" -#include "TextureIndex.hpp" +#include "../app/Environment.hpp" +#include "../app/TextureIndex.hpp" #include "../net/io.hpp" #include namespace blank { +namespace server { ServerState::ServerState( HeadlessEnvironment &env, @@ -64,3 +65,4 @@ void ServerState::Render(Viewport &viewport) { } } +} diff --git a/src/app/ServerState.hpp b/src/server/ServerState.hpp similarity index 81% rename from src/app/ServerState.hpp rename to src/server/ServerState.hpp index db21d0f..8f564ab 100644 --- a/src/app/ServerState.hpp +++ b/src/server/ServerState.hpp @@ -1,11 +1,11 @@ -#ifndef BLANK_APP_SERVERSTATE_HPP_ -#define BLANK_APP_SERVERSTATE_HPP_ +#ifndef BLANK_SERVER_SERVERSTATE_HPP_ +#define BLANK_SERVER_SERVERSTATE_HPP_ -#include "IntervalTimer.hpp" -#include "State.hpp" +#include "Server.hpp" #include "../ai/Spawner.hpp" +#include "../app/IntervalTimer.hpp" +#include "../app/State.hpp" #include "../model/Skeletons.hpp" -#include "../net/Server.hpp" #include "../world/BlockTypeRegistry.hpp" #include "../world/ChunkLoader.hpp" #include "../world/Generator.hpp" @@ -17,6 +17,8 @@ namespace blank { class HeadlessEnvironment; class WorldSave; +namespace server { + class ServerState : public State { @@ -46,6 +48,7 @@ private: }; +} } #endif diff --git a/src/server/net.cpp b/src/server/net.cpp new file mode 100644 index 0000000..0cf0cdd --- /dev/null +++ b/src/server/net.cpp @@ -0,0 +1,556 @@ +#include "ClientConnection.hpp" +#include "ChunkTransmitter.hpp" +#include "Server.hpp" + +#include "../app/init.hpp" +#include "../world/ChunkIndex.hpp" +#include "../world/Entity.hpp" +#include "../world/World.hpp" + +#include +#include + +using namespace std; + + +namespace blank { +namespace server { + +ChunkTransmitter::ChunkTransmitter(ClientConnection &conn) +: conn(conn) +, current(nullptr) +, buffer_size(Chunk::BlockSize() + 10) +, buffer(new uint8_t[buffer_size]) +, buffer_len(0) +, packet_len(Packet::ChunkData::MAX_DATA_LEN) +, cursor(0) +, num_packets(0) +, begin_packet(-1) +, data_packets() +, confirm_wait(0) +, trans_id(0) +, compressed(false) { + +} + +ChunkTransmitter::~ChunkTransmitter() { + Abort(); +} + +bool ChunkTransmitter::Idle() const noexcept { + return !Transmitting() && !Waiting(); +} + +bool ChunkTransmitter::Transmitting() const noexcept { + return cursor < num_packets; +} + +void ChunkTransmitter::Transmit() { + if (cursor < num_packets) { + SendData(cursor); + ++cursor; + } +} + +bool ChunkTransmitter::Waiting() const noexcept { + return confirm_wait > 0; +} + +void ChunkTransmitter::Ack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + begin_packet = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + for (int i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + data_packets[i] = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + } +} + +void ChunkTransmitter::Nack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + SendBegin(); + return; + } + for (size_t i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + SendData(i); + return; + } + } +} + +void ChunkTransmitter::Abort() { + if (!current) return; + + Release(); + + begin_packet = -1; + data_packets.clear(); + confirm_wait = 0; +} + +void ChunkTransmitter::Send(Chunk &chunk) { + // abort current chunk, if any + Abort(); + + current = &chunk; + current->Ref(); + + // load new chunk data + compressed = true; + buffer_len = buffer_size; + if (compress(buffer.get(), &buffer_len, reinterpret_cast(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) { + // compression failed, send it uncompressed + buffer_len = Chunk::BlockSize(); + memcpy(buffer.get(), chunk.BlockData(), buffer_len); + compressed = false; + } + cursor = 0; + num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0); + data_packets.resize(num_packets, -1); + + ++trans_id; + SendBegin(); +} + +void ChunkTransmitter::SendBegin() { + uint32_t flags = compressed; + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteFlags(flags); + pack.WriteChunkCoords(current->Position()); + pack.WriteDataSize(buffer_len); + if (begin_packet == -1) { + ++confirm_wait; + } + begin_packet = conn.Send(); +} + +void ChunkTransmitter::SendData(size_t i) { + int pos = i * packet_len; + int len = min(packet_len, buffer_len - pos); + const uint8_t *data = &buffer[pos]; + + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteDataOffset(pos); + pack.WriteDataSize(len); + pack.WriteData(data, len); + + if (data_packets[i] == -1) { + ++confirm_wait; + } + data_packets[i] = conn.Send(); +} + +void ChunkTransmitter::Release() { + if (current) { + current->UnRef(); + current = nullptr; + } +} + + +ClientConnection::ClientConnection(Server &server, const IPaddress &addr) +: server(server) +, conn(addr) +, player(nullptr, nullptr) +, spawns() +, confirm_wait(0) +, player_update_state() +, player_update_pack(0) +, player_update_timer(1500) +, transmitter(*this) +, chunk_queue() +, old_base() { + conn.SetHandler(this); +} + +ClientConnection::~ClientConnection() { + DetachPlayer(); +} + +void ClientConnection::Update(int dt) { + conn.Update(dt); + if (Disconnected()) { + return; + } + if (HasPlayer()) { + // sync entities + auto global_iter = server.GetWorld().Entities().begin(); + auto global_end = server.GetWorld().Entities().end(); + auto local_iter = spawns.begin(); + auto local_end = spawns.end(); + + while (global_iter != global_end && local_iter != local_end) { + if (global_iter->ID() == local_iter->entity->ID()) { + // they're the same + if (CanDespawn(*global_iter)) { + SendDespawn(*local_iter); + } else { + // update + SendUpdate(*local_iter); + } + ++global_iter; + ++local_iter; + } else if (global_iter->ID() < local_iter->entity->ID()) { + // global entity was inserted + if (CanSpawn(*global_iter)) { + auto spawned = spawns.emplace(local_iter, *global_iter); + SendSpawn(*spawned); + } + ++global_iter; + } else { + // global entity was removed + SendDespawn(*local_iter); + ++local_iter; + } + } + + // leftover spawns + while (global_iter != global_end) { + if (CanSpawn(*global_iter)) { + spawns.emplace_back(*global_iter); + SendSpawn(spawns.back()); + } + ++global_iter; + } + + // leftover despawns + while (local_iter != local_end) { + SendDespawn(*local_iter); + ++local_iter; + } + + CheckPlayerFix(); + CheckChunkQueue(); + } + if (conn.ShouldPing()) { + conn.SendPing(server.GetPacket(), server.GetSocket()); + } +} + +ClientConnection::SpawnStatus::SpawnStatus(Entity &e) +: entity(&e) +, spawn_pack(-1) +, despawn_pack(-1) { + entity->Ref(); +} + +ClientConnection::SpawnStatus::~SpawnStatus() { + entity->UnRef(); +} + +bool ClientConnection::CanSpawn(const Entity &e) const noexcept { + return + &e != player.entity && + !e.Dead() && + manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; +} + +bool ClientConnection::CanDespawn(const Entity &e) const noexcept { + return + e.Dead() || + manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) > 7; +} + +uint16_t ClientConnection::Send() { + return conn.Send(server.GetPacket(), server.GetSocket()); +} + +uint16_t ClientConnection::Send(size_t len) { + server.GetPacket().len = len; + return Send(); +} + +void ClientConnection::SendSpawn(SpawnStatus &status) { + // don't double spawn + if (status.spawn_pack != -1) return; + + auto pack = Prepare(); + pack.WriteEntity(*status.entity); + status.spawn_pack = Send(); + ++confirm_wait; +} + +void ClientConnection::SendDespawn(SpawnStatus &status) { + // don't double despawn + if (status.despawn_pack != -1) return; + + auto pack = Prepare(); + pack.WriteEntityID(status.entity->ID()); + status.despawn_pack = Send(); + ++confirm_wait; +} + +void ClientConnection::SendUpdate(SpawnStatus &status) { + // don't send updates while spawn not ack'd or despawn sent + if (status.spawn_pack != -1 || status.despawn_pack != -1) return; + + // TODO: pack entity updates + auto pack = Prepare(); + pack.WriteEntityCount(1); + pack.WriteEntity(*status.entity, 0); + Send(Packet::EntityUpdate::GetSize(1)); +} + +void ClientConnection::CheckPlayerFix() { + // player_update_state's position holds the client's most recent prediction + glm::vec3 diff = player_update_state.Diff(PlayerEntity().GetState()); + float dist_squared = dot(diff, diff); + + // if client's prediction is off by more than 1cm, send + // our (authoritative) state back so it can fix it + constexpr float fix_thresh = 0.0001f; + + if (dist_squared > fix_thresh) { + auto pack = Prepare(); + pack.WritePacketSeq(player_update_pack); + pack.WritePlayer(PlayerEntity()); + Send(); + } +} + +void ClientConnection::CheckChunkQueue() { + if (PlayerChunks().Base() != old_base) { + Chunk::Pos begin = PlayerChunks().CoordsBegin(); + Chunk::Pos end = PlayerChunks().CoordsEnd(); + for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { + for (pos.y = begin.y; pos.y < end.y; ++pos.y) { + for (pos.x = begin.x; pos.x < end.x; ++pos.x) { + if (manhattan_radius(pos - old_base) > PlayerChunks().Extent()) { + chunk_queue.push_back(pos); + } + } + } + } + old_base = PlayerChunks().Base(); + } + if (transmitter.Transmitting()) { + transmitter.Transmit(); + return; + } + if (transmitter.Idle()) { + int count = 0; + constexpr int max = 64; + while (count < max && !chunk_queue.empty()) { + Chunk::Pos pos = chunk_queue.front(); + chunk_queue.pop_front(); + if (PlayerChunks().InRange(pos)) { + Chunk *chunk = PlayerChunks().Get(pos); + if (chunk) { + transmitter.Send(*chunk); + return; + } else { + chunk_queue.push_back(pos); + } + ++count; + } + } + } +} + +void ClientConnection::AttachPlayer(const Player &new_player) { + DetachPlayer(); + player = new_player; + player.entity->Ref(); + + old_base = player.chunks->Base(); + Chunk::Pos begin = player.chunks->CoordsBegin(); + Chunk::Pos end = player.chunks->CoordsEnd(); + for (Chunk::Pos pos = begin; pos.z < end.z; ++pos.z) { + for (pos.y = begin.y; pos.y < end.y; ++pos.y) { + for (pos.x = begin.x; pos.x < end.x; ++pos.x) { + chunk_queue.push_back(pos); + } + } + } + + cout << "player \"" << player.entity->Name() << "\" joined" << endl; +} + +void ClientConnection::DetachPlayer() { + if (!HasPlayer()) return; + cout << "player \"" << player.entity->Name() << "\" left" << endl; + player.entity->Kill(); + player.entity->UnRef(); + player.entity = nullptr; + player.chunks = nullptr; + transmitter.Abort(); + chunk_queue.clear(); +} + +void ClientConnection::OnPacketReceived(uint16_t seq) { + if (transmitter.Waiting()) { + transmitter.Ack(seq); + } + if (!confirm_wait) return; + for (auto iter = spawns.begin(), end = spawns.end(); iter != end; ++iter) { + if (seq == iter->spawn_pack) { + iter->spawn_pack = -1; + --confirm_wait; + return; + } + if (seq == iter->despawn_pack) { + spawns.erase(iter); + --confirm_wait; + return; + } + } +} + +void ClientConnection::OnPacketLost(uint16_t seq) { + if (transmitter.Waiting()) { + transmitter.Nack(seq); + } + if (!confirm_wait) return; + for (SpawnStatus &status : spawns) { + if (seq == status.spawn_pack) { + status.spawn_pack = -1; + --confirm_wait; + SendSpawn(status); + return; + } + if (seq == status.despawn_pack) { + status.despawn_pack = -1; + --confirm_wait; + SendDespawn(status); + return; + } + } +} + +void ClientConnection::On(const Packet::Login &pack) { + string name; + pack.ReadPlayerName(name); + + Player new_player = server.GetWorld().AddPlayer(name); + + if (new_player.entity) { + // success! + AttachPlayer(new_player); + cout << "accepted login from player \"" << name << '"' << endl; + auto response = Prepare(); + response.WritePlayer(*new_player.entity); + response.WriteWorldName(server.GetWorld().Name()); + Send(); + // set up update tracking + player_update_state = new_player.entity->GetState(); + player_update_pack = pack.Seq(); + player_update_timer.Reset(); + player_update_timer.Start(); + } else { + // aw no :( + cout << "rejected login from player \"" << name << '"' << endl; + Prepare(); + Send(); + conn.Close(); + } +} + +void ClientConnection::On(const Packet::Part &) { + conn.Close(); +} + +void ClientConnection::On(const Packet::PlayerUpdate &pack) { + if (!HasPlayer()) return; + int pack_diff = int16_t(pack.Seq()) - int16_t(player_update_pack); + bool overdue = player_update_timer.HitOnce(); + player_update_timer.Reset(); + if (pack_diff > 0 || overdue) { + player_update_pack = pack.Seq(); + pack.ReadPlayerState(player_update_state); + // accept velocity and orientation as "user input" + PlayerEntity().Velocity(player_update_state.velocity); + PlayerEntity().Orientation(player_update_state.orient); + } +} + + +Server::Server(const Config &conf, World &world) +: serv_sock(nullptr) +, serv_pack{ -1, nullptr, 0 } +, clients() +, world(world) { + serv_sock = SDLNet_UDP_Open(conf.port); + if (!serv_sock) { + throw NetError("SDLNet_UDP_Open"); + } + + serv_pack.data = new Uint8[sizeof(Packet)]; + serv_pack.maxlen = sizeof(Packet); +} + +Server::~Server() { + delete[] serv_pack.data; + SDLNet_UDP_Close(serv_sock); +} + + +void Server::Handle() { + int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + while (result > 0) { + HandlePacket(serv_pack); + result = SDLNet_UDP_Recv(serv_sock, &serv_pack); + } + if (result == -1) { + // a boo boo happened + throw NetError("SDLNet_UDP_Recv"); + } +} + +void Server::HandlePacket(const UDPpacket &udp_pack) { + if (udp_pack.len < int(sizeof(Packet::Header))) { + // packet too small, drop + return; + } + const Packet &pack = *reinterpret_cast(udp_pack.data); + if (pack.header.tag != Packet::TAG) { + // mistagged packet, drop + return; + } + + ClientConnection &client = GetClient(udp_pack.address); + client.GetConnection().Received(udp_pack); +} + +ClientConnection &Server::GetClient(const IPaddress &addr) { + for (ClientConnection &client : clients) { + if (client.Matches(addr)) { + return client; + } + } + clients.emplace_back(*this, addr); + return clients.back(); +} + +void Server::Update(int dt) { + for (list::iterator client(clients.begin()), end(clients.end()); client != end;) { + client->Update(dt); + if (client->Disconnected()) { + client = clients.erase(client); + } else { + ++client; + } + } +} + +} +} -- 2.39.2