X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fserver%2Fnet.cpp;h=e88ca1fc28b93c577ebb7676a334cbc2b0e05039;hb=20d0a76d2519c71009c3b3babec0df27529f8142;hp=68880c7d6fce87ab7b9f1db1f461b752e6bd24b5;hpb=8fdc24f0b3fb287f5d4e1c7d1f85ad85d5ed2414;p=blank.git diff --git a/src/server/net.cpp b/src/server/net.cpp index 68880c7..e88ca1f 100644 --- a/src/server/net.cpp +++ b/src/server/net.cpp @@ -3,13 +3,17 @@ #include "Server.hpp" #include "../app/init.hpp" -#include "../model/CompositeModel.hpp" +#include "../geometry/distance.hpp" +#include "../io/WorldSave.hpp" +#include "../model/Model.hpp" #include "../world/ChunkIndex.hpp" #include "../world/Entity.hpp" #include "../world/World.hpp" +#include #include #include +#include using namespace std; @@ -158,7 +162,7 @@ void ChunkTransmitter::SendData(size_t i) { if (data_packets[i] == -1) { ++confirm_wait; } - data_packets[i] = conn.Send(); + data_packets[i] = conn.Send(Packet::ChunkData::GetSize(len)); } void ChunkTransmitter::Release() { @@ -172,17 +176,20 @@ void ChunkTransmitter::Release() { ClientConnection::ClientConnection(Server &server, const IPaddress &addr) : server(server) , conn(addr) -, player(nullptr, nullptr) +, input() , player_model(nullptr) , spawns() , confirm_wait(0) , entity_updates() +, entity_updates_skipped(0) , player_update_state() , player_update_pack(0) , player_update_timer(1500) +, old_actions(0) , transmitter(*this) , chunk_queue() -, old_base() { +, old_base() +, chunk_blocks_skipped(0) { conn.SetHandler(this); } @@ -196,58 +203,60 @@ void ClientConnection::Update(int dt) { 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 - QueueUpdate(*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 + CheckPlayerFix(); + CheckChunkQueue(); + CheckEntities(); + SendUpdates(); + } + if (conn.ShouldPing()) { + conn.SendPing(server.GetPacket(), server.GetSocket()); + } +} + +void ClientConnection::CheckEntities() { + 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); - ++local_iter; + } else if (SendingUpdates()) { + // update + QueueUpdate(*local_iter); } - } - - // leftover spawns - while (global_iter != global_end) { + ++global_iter; + ++local_iter; + } else if (global_iter->ID() < local_iter->entity->ID()) { + // global entity was inserted if (CanSpawn(*global_iter)) { - spawns.emplace_back(*global_iter); - SendSpawn(spawns.back()); + auto spawned = spawns.emplace(local_iter, *global_iter); + SendSpawn(*spawned); } ++global_iter; - } - - // leftover despawns - while (local_iter != local_end) { + } else { + // global entity was removed SendDespawn(*local_iter); ++local_iter; } - SendUpdates(); + } - CheckPlayerFix(); - CheckChunkQueue(); + // leftover spawns + while (global_iter != global_end) { + if (CanSpawn(*global_iter)) { + spawns.emplace_back(*global_iter); + SendSpawn(spawns.back()); + } + ++global_iter; } - if (conn.ShouldPing()) { - conn.SendPing(server.GetPacket(), server.GetSocket()); + + // leftover despawns + while (local_iter != local_end) { + SendDespawn(*local_iter); + ++local_iter; } } @@ -264,7 +273,7 @@ ClientConnection::SpawnStatus::~SpawnStatus() { bool ClientConnection::CanSpawn(const Entity &e) const noexcept { return - &e != player.entity && + &e != &PlayerEntity() && !e.Dead() && manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; } @@ -280,7 +289,7 @@ uint16_t ClientConnection::Send() { } uint16_t ClientConnection::Send(size_t len) { - server.GetPacket().len = len; + server.GetPacket().len = sizeof(Packet::Header) + len; return Send(); } @@ -304,6 +313,10 @@ void ClientConnection::SendDespawn(SpawnStatus &status) { ++confirm_wait; } +bool ClientConnection::SendingUpdates() const noexcept { + return entity_updates_skipped >= NetStat().SuggestedPacketHold(); +} + void ClientConnection::QueueUpdate(SpawnStatus &status) { // don't send updates while spawn not ack'd or despawn sent if (status.spawn_pack == -1 && status.despawn_pack == -1) { @@ -312,10 +325,17 @@ void ClientConnection::QueueUpdate(SpawnStatus &status) { } void ClientConnection::SendUpdates() { + if (!SendingUpdates()) { + entity_updates.clear(); + ++entity_updates_skipped; + return; + } + auto base = PlayerChunks().Base(); auto pack = Prepare(); + pack.WriteChunkBase(base); int entity_pos = 0; for (SpawnStatus *status : entity_updates) { - pack.WriteEntity(*status->entity, entity_pos); + pack.WriteEntity(*status->entity, base, entity_pos); ++entity_pos; if (entity_pos == Packet::EntityUpdate::MAX_ENTITIES) { pack.WriteEntityCount(entity_pos); @@ -329,12 +349,13 @@ void ClientConnection::SendUpdates() { Send(Packet::EntityUpdate::GetSize(entity_pos)); } entity_updates.clear(); + entity_updates_skipped = 0; } 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); + float dist_squared = glm::length2(diff); // if client's prediction is off by more than 1cm, send // our (authoritative) state back so it can fix it @@ -348,11 +369,28 @@ void ClientConnection::CheckPlayerFix() { } } +namespace { + +struct QueueCompare { + explicit QueueCompare(const glm::ivec3 &base) + : base(base) { } + bool operator ()(const glm::ivec3 &left, const glm::ivec3 &right) const noexcept { + const glm::ivec3 ld(left - base); + const glm::ivec3 rd(right - base); + return + ld.x * ld.x + ld.y * ld.y + ld.z * ld.z < + rd.x * rd.x + rd.y * rd.y + rd.z * rd.z; + } + const glm::ivec3 &base; +}; + +} + 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) { + ExactLocation::Coarse begin = PlayerChunks().CoordsBegin(); + ExactLocation::Coarse end = PlayerChunks().CoordsEnd(); + for (ExactLocation::Coarse 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()) { @@ -362,21 +400,30 @@ void ClientConnection::CheckChunkQueue() { } } old_base = PlayerChunks().Base(); + sort(chunk_queue.begin(), chunk_queue.end(), QueueCompare(old_base)); + chunk_queue.erase(unique(chunk_queue.begin(), chunk_queue.end()), chunk_queue.end()); + } + // don't push entity updates and chunk data in the same tick + if (chunk_blocks_skipped >= NetStat().SuggestedPacketHold() && !SendingUpdates()) { + ++chunk_blocks_skipped; + return; } if (transmitter.Transmitting()) { transmitter.Transmit(); + chunk_blocks_skipped = 0; return; } if (transmitter.Idle()) { int count = 0; constexpr int max = 64; while (count < max && !chunk_queue.empty()) { - Chunk::Pos pos = chunk_queue.front(); + ExactLocation::Coarse pos = chunk_queue.front(); chunk_queue.pop_front(); if (PlayerChunks().InRange(pos)) { Chunk *chunk = PlayerChunks().Get(pos); if (chunk) { transmitter.Send(*chunk); + chunk_blocks_skipped = 0; return; } else { chunk_queue.push_back(pos); @@ -387,40 +434,51 @@ void ClientConnection::CheckChunkQueue() { } } -void ClientConnection::AttachPlayer(const Player &new_player) { +void ClientConnection::AttachPlayer(Player &player) { DetachPlayer(); - player = new_player; - player.entity->Ref(); + input.reset(new DirectInput(server.GetWorld(), player, server)); + PlayerEntity().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) { + cli_ctx.reset(new NetworkCLIFeedback(player, *this)); + + old_base = PlayerChunks().Base(); + ExactLocation::Coarse begin = PlayerChunks().CoordsBegin(); + ExactLocation::Coarse end = PlayerChunks().CoordsEnd(); + for (ExactLocation::Coarse 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); } } } + sort(chunk_queue.begin(), chunk_queue.end(), QueueCompare(old_base)); + // TODO: should the server do this? if (HasPlayerModel()) { - GetPlayerModel().Instantiate(player.entity->GetModel()); + GetPlayerModel().Instantiate(PlayerEntity().GetModel()); } - cout << "player \"" << player.entity->Name() << "\" joined" << endl; + string msg = "player \"" + player.Name() + "\" joined"; + cout << msg << endl; + server.DistributeMessage(0, 0, msg); } 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; + string msg = "player \"" + input->GetPlayer().Name() + "\" left"; + cout << msg << endl; + server.DistributeMessage(0, 0, msg); + + server.GetWorldSave().Write(input->GetPlayer()); + PlayerEntity().Kill(); + PlayerEntity().UnRef(); + cli_ctx.reset(); + input.reset(); transmitter.Abort(); chunk_queue.clear(); + old_actions = 0; } -void ClientConnection::SetPlayerModel(const CompositeModel &m) noexcept { +void ClientConnection::SetPlayerModel(const Model &m) noexcept { player_model = &m; if (HasPlayer()) { m.Instantiate(PlayerEntity().GetModel()); @@ -431,7 +489,7 @@ bool ClientConnection::HasPlayerModel() const noexcept { return player_model; } -const CompositeModel &ClientConnection::GetPlayerModel() const noexcept { +const Model &ClientConnection::GetPlayerModel() const noexcept { return *player_model; } @@ -479,18 +537,18 @@ void ClientConnection::On(const Packet::Login &pack) { string name; pack.ReadPlayerName(name); - Player new_player = server.GetWorld().AddPlayer(name); + Player *new_player = server.JoinPlayer(name); - if (new_player.entity) { + if (new_player) { // success! - AttachPlayer(new_player); + AttachPlayer(*new_player); cout << "accepted login from player \"" << name << '"' << endl; auto response = Prepare(); - response.WritePlayer(*new_player.entity); + response.WritePlayer(new_player->GetEntity()); response.WriteWorldName(server.GetWorld().Name()); Send(); // set up update tracking - player_update_state = new_player.entity->GetState(); + player_update_state = new_player->GetEntity().GetState(); player_update_pack = pack.Seq(); player_update_timer.Reset(); player_update_timer.Start(); @@ -512,37 +570,161 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) { 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); + if (pack_diff <= 0 && !overdue) { + // drop old packets if we have a fairly recent state + return; + } + glm::vec3 movement(0.0f); + uint8_t new_actions; + uint8_t slot; + + player_update_pack = pack.Seq(); + pack.ReadPredictedState(player_update_state); + pack.ReadMovement(movement); + pack.ReadActions(new_actions); + pack.ReadSlot(slot); + + // accept client's orientation as is + input->GetPlayer().GetEntity().Orientation(player_update_state.orient); + // simulate movement + input->SetMovement(movement); + // rotate head to match client's "prediction" + input->TurnHead(player_update_state.pitch - input->GetPitch(), player_update_state.yaw - input->GetYaw()); + // select the given inventory slot + input->SelectInventory(slot); + + // check if any actions have been started or stopped + if ((new_actions & 0x01) && !(old_actions & 0x01)) { + input->StartPrimaryAction(); + } else if (!(new_actions & 0x01) && (old_actions & 0x01)) { + input->StopPrimaryAction(); } + if ((new_actions & 0x02) && !(old_actions & 0x02)) { + input->StartSecondaryAction(); + } else if (!(new_actions & 0x02) && (old_actions & 0x02)) { + input->StopSecondaryAction(); + } + if ((new_actions & 0x04) && !(old_actions & 0x04)) { + input->StartTertiaryAction(); + } else if (!(new_actions & 0x04) && (old_actions & 0x04)) { + input->StopTertiaryAction(); + } + old_actions = new_actions; +} + +bool ClientConnection::ChunkInRange(const glm::ivec3 &pos) const noexcept { + return HasPlayer() && PlayerChunks().InRange(pos); } +void ClientConnection::On(const Packet::ChunkBegin &pack) { + glm::ivec3 pos; + pack.ReadChunkCoords(pos); + if (ChunkInRange(pos)) { + chunk_queue.push_front(pos); + } +} -Server::Server(const Config &conf, World &world) +void ClientConnection::On(const Packet::Message &pack) { + uint8_t type; + uint32_t ref; + string msg; + pack.ReadType(type); + pack.ReadReferral(ref); + pack.ReadMessage(msg); + + if (type == 1 && cli_ctx) { + server.DispatchMessage(*cli_ctx, msg); + } +} + +uint16_t ClientConnection::SendMessage(uint8_t type, uint32_t from, const string &msg) { + auto pack = Prepare(); + pack.WriteType(type); + pack.WriteReferral(from); + pack.WriteMessage(msg); + return Send(Packet::Message::GetSize(msg)); +} + + +NetworkCLIFeedback::NetworkCLIFeedback(Player &p, ClientConnection &c) +: CLIContext(p) +, conn(c) { + +} + +void NetworkCLIFeedback::Error(const string &msg) { + conn.SendMessage(0, 0, msg); +} + +void NetworkCLIFeedback::Message(const string &msg) { + conn.SendMessage(0, 0, msg); +} + +void NetworkCLIFeedback::Broadcast(const string &msg) { + conn.GetServer().DistributeMessage(0, GetPlayer().GetEntity().ID(), msg); +} + + +// relying on {} zero intitialization for UDPpacket, because +// the type and number of fields is not well defined +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +Server::Server( + const Config::Network &conf, + World &world, + const World::Config &wc, + const WorldSave &save) : serv_sock(nullptr) , serv_pack{ -1, nullptr, 0 } +, serv_set(SDLNet_AllocSocketSet(1)) , clients() , world(world) -, player_model(nullptr) { +, spawn_index(world.Chunks().MakeIndex(wc.spawn, 3)) +, save(save) +, player_model(nullptr) +, cli(world) { +#pragma GCC diagnostic pop + if (!serv_set) { + throw NetError("SDLNet_AllocSocketSet"); + } + serv_sock = SDLNet_UDP_Open(conf.port); if (!serv_sock) { + SDLNet_FreeSocketSet(serv_set); throw NetError("SDLNet_UDP_Open"); } + if (SDLNet_UDP_AddSocket(serv_set, serv_sock) == -1) { + SDLNet_UDP_Close(serv_sock); + SDLNet_FreeSocketSet(serv_set); + throw NetError("SDLNet_UDP_AddSocket"); + } + serv_pack.data = new Uint8[sizeof(Packet)]; serv_pack.maxlen = sizeof(Packet); } Server::~Server() { + for (ClientConnection &client : clients) { + client.Disconnected(); + } + clients.clear(); + world.Chunks().UnregisterIndex(spawn_index); delete[] serv_pack.data; + SDLNet_UDP_DelSocket(serv_set, serv_sock); SDLNet_UDP_Close(serv_sock); + SDLNet_FreeSocketSet(serv_set); } +void Server::Wait(int dt) noexcept { + SDLNet_CheckSockets(serv_set, dt); +} + +bool Server::Ready() noexcept { + return SDLNet_CheckSockets(serv_set, 0) > 0; +} + void Server::Handle() { int result = SDLNet_UDP_Recv(serv_sock, &serv_pack); while (result > 0) { @@ -585,6 +767,7 @@ ClientConnection &Server::GetClient(const IPaddress &addr) { 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 { @@ -593,7 +776,7 @@ void Server::Update(int dt) { } } -void Server::SetPlayerModel(const CompositeModel &m) noexcept { +void Server::SetPlayerModel(const Model &m) noexcept { player_model = &m; for (ClientConnection &client : clients) { client.SetPlayerModel(m); @@ -604,9 +787,67 @@ bool Server::HasPlayerModel() const noexcept { return player_model; } -const CompositeModel &Server::GetPlayerModel() const noexcept { +const Model &Server::GetPlayerModel() const noexcept { return *player_model; } +Player *Server::JoinPlayer(const string &name) { + if (spawn_index.MissingChunks() > 0) { + return nullptr; + } + Player *player = world.AddPlayer(name); + if (!player) { + return nullptr; + } + if (save.Exists(*player)) { + save.Read(*player); + } else { + // TODO: spawn + } + return player; +} + +void Server::SetBlock(Chunk &chunk, int index, const Block &block) { + chunk.SetBlock(index, block); + // TODO: batch chunk changes + auto pack = Packet::Make(GetPacket()); + pack.WriteChunkCoords(chunk.Position()); + pack.WriteBlockCount(uint32_t(1)); + pack.WriteIndex(index, 0); + pack.WriteBlock(chunk.BlockAt(index), 0); + GetPacket().len = sizeof(Packet::Header) + Packet::BlockUpdate::GetSize(1); + for (ClientConnection &client : clients) { + if (client.ChunkInRange(chunk.Position())) { + client.Send(); + } + } +} + +void Server::DispatchMessage(CLIContext &ctx, const string &msg) { + if (msg.empty()) { + return; + } + if (msg[0] == '/' && msg.size() > 1 && msg[1] != '/') { + cli.Execute(ctx, msg.substr(1)); + } else { + DistributeMessage(1, ctx.GetPlayer().GetEntity().ID(), msg); + } +} + +void Server::DistributeMessage(uint8_t type, uint32_t ref, const string &msg) { + auto pack = Packet::Make(serv_pack); + pack.WriteType(type); + pack.WriteReferral(ref); + pack.WriteMessage(msg); + serv_pack.len = sizeof(Packet::Header) + Packet::Message::GetSize(msg); + SendAll(); +} + +void Server::SendAll() { + for (ClientConnection &client : clients) { + client.GetConnection().Send(serv_pack, serv_sock); + } +} + } }