From: Daniel Karbach Date: Thu, 17 Sep 2015 09:23:19 +0000 (+0200) Subject: transmit chunks from server to client X-Git-Url: http://git.localhorst.tv/?a=commitdiff_plain;h=ae5a7e7d8517fac406a88e9bf98fd3d5bb1728b9;p=blank.git transmit chunks from server to client for now, the server decides which chunks to send later on, the client should request chunks from the server with an optional cache tag to avoid unnecessary transmission --- diff --git a/TODO b/TODO index b77cdae..4ae41cc 100644 --- a/TODO +++ b/TODO @@ -34,7 +34,11 @@ block asset loading networking - exchange of chunks and entities + write tests + do some manual testing + some more testing + a little optimization + and give players an appearance as well ^^ launcher ui diff --git a/doc/protocol b/doc/protocol index 749e099..607bbb1 100644 --- a/doc/protocol +++ b/doc/protocol @@ -147,3 +147,32 @@ Payload: 0 sequence number of the offending packet, 16bit unsigned int 2 entity state of the player's entity on the server Length: 66 + + +Chunk Begin +----------- + +Sent by the server to inform the client of an upcoming chunk transmission. + +Code: 9 +Payload: + 0 transmission ID, used for reference with Chunk Data packets, 32bit unsigned int + 4 flags, 32bit bitfield with boolean values + 1: compressed + 8 chunk coordinates, vec3i + 20 data size, 32bit unsigned int +Length: 24 + + +Chunk Data +---------- + +Raw chunk data sent by the server, optionally compressed with zlib. + +Code: 10 +Payload: + 0 transmission ID, references the Chunk Begin packet this data belongs to, 32bit unsigned int + 4 block offset, offset of this block inside the whole data, 32bit unsigned int + 8 block size, size of the data block, 32bit unsigned int + 12 data, raw data +Length: 12-484 diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index 09a9919..39b393a 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -5,6 +5,7 @@ #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" @@ -30,6 +31,7 @@ public: World &GetWorld() noexcept { return world; } Interface &GetInterface() noexcept { return interface; } + ChunkReceiver &GetChunkReceiver() noexcept { return chunk_receiver; } Skeletons &GetSkeletons() noexcept { return skeletons; } void OnEnter() override; @@ -47,6 +49,7 @@ private: WorldSave save; World world; Interface interface; + ChunkReceiver chunk_receiver; ChunkRenderer chunk_renderer; Skeletons skeletons; IntervalTimer loop_timer; diff --git a/src/client/MasterState.hpp b/src/client/MasterState.hpp index aaea6a9..ff1769f 100644 --- a/src/client/MasterState.hpp +++ b/src/client/MasterState.hpp @@ -56,6 +56,8 @@ public: void On(const Packet::DespawnEntity &) override; void On(const Packet::EntityUpdate &) override; void On(const Packet::PlayerCorrection &) override; + void On(const Packet::ChunkBegin &) override; + void On(const Packet::ChunkData &) override; private: /// flag entity as updated by given packet diff --git a/src/client/client.cpp b/src/client/client.cpp index 72d860d..4be9dbe 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -54,6 +54,7 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id) world, world.AddPlayer(master.GetInterfaceConf().player_name, player_id) ) +, chunk_receiver(world.Chunks()) , chunk_renderer(*interface.GetPlayer().chunks) , skeletons() , loop_timer(16) @@ -103,6 +104,7 @@ void InteractiveState::Handle(const SDL_Event &event) { void InteractiveState::Update(int dt) { loop_timer.Update(dt); master.Update(dt); + chunk_receiver.Update(dt); interface.Update(dt); int world_dt = 0; @@ -417,5 +419,23 @@ void MasterState::On(const Packet::PlayerCorrection &pack) { state->MergePlayerCorrection(pack_seq, corrected_state); } +void MasterState::On(const Packet::ChunkBegin &pack) { + if (!state) { + cout << "got chunk data, but the world has not been created yet" << endl; + cout << "great, this will totally screw up everything :(" << endl; + return; + } + state->GetChunkReceiver().Handle(pack); +} + +void MasterState::On(const Packet::ChunkData &pack) { + if (!state) { + cout << "got chunk data, but the world has not been created yet" << endl; + cout << "great, this will totally screw up everything :(" << endl; + return; + } + state->GetChunkReceiver().Handle(pack); +} + } } diff --git a/src/net/ChunkReceiver.hpp b/src/net/ChunkReceiver.hpp new file mode 100644 index 0000000..40acc2f --- /dev/null +++ b/src/net/ChunkReceiver.hpp @@ -0,0 +1,40 @@ +#ifndef BLANK_NET_CHUNKRECEIVER_HPP_ +#define BLANK_NET_CHUNKRECEIVER_HPP_ + +#include "Packet.hpp" +#include "../app/IntervalTimer.hpp" + +#include +#include + + +namespace blank { + +class ChunkStore; +class ChunkTransmission; + +class ChunkReceiver { + +public: + explicit ChunkReceiver(ChunkStore &); + ~ChunkReceiver(); + + void Update(int dt); + + void Handle(const Packet::ChunkBegin &); + void Handle(const Packet::ChunkData &); + +private: + ChunkTransmission &GetTransmission(std::uint32_t id); + void Commit(ChunkTransmission &); + +private: + ChunkStore &store; + std::list transmissions; + IntervalTimer timer; + +}; + +} + +#endif diff --git a/src/net/ChunkTransmission.hpp b/src/net/ChunkTransmission.hpp new file mode 100644 index 0000000..90dd35e --- /dev/null +++ b/src/net/ChunkTransmission.hpp @@ -0,0 +1,40 @@ +#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_ +#define BLANK_NET_CHUNKTRANSMISSION_HPP_ + +#include "../world/Chunk.hpp" + +#include +#include + + +namespace blank { + +struct ChunkTransmission { + + std::uint32_t id; + std::uint32_t flags; + glm::ivec3 coords; + std::uint32_t data_size; + std::uint32_t data_received; + + int last_update; + + bool header_received; + bool active; + + std::uint8_t buffer[Chunk::BlockSize() + 10]; + + + ChunkTransmission(); + + void Clear() noexcept; + + bool Complete() const noexcept; + + bool Compressed() const noexcept; + +}; + +}; + +#endif diff --git a/src/net/ChunkTransmitter.hpp b/src/net/ChunkTransmitter.hpp new file mode 100644 index 0000000..44f6bd8 --- /dev/null +++ b/src/net/ChunkTransmitter.hpp @@ -0,0 +1,73 @@ +#ifndef BLANK_NET_CHUNKTRANSMITTER_HPP_ +#define BLANK_NET_CHUNKTRANSMITTER_HPP_ + +#include +#include +#include + + +namespace blank { + +class Chunk; +class ClientConnection; + +class ChunkTransmitter { + +public: + explicit ChunkTransmitter(ClientConnection &); + ~ChunkTransmitter(); + + /// Returns true if not transmitting or waiting on acks, so + /// the next chunk may be queued without schmutzing up anything. + bool Idle() const noexcept; + + /// Returns true if a transmission is still going on, + /// meaning there's at least one packet that needs to + /// be sent. + bool Transmitting() const noexcept; + /// Send the next packet of the current chunk (if any). + void Transmit(); + + /// Returns true if there's one or more packets which + /// still have to be ack'd by the remote. + bool Waiting() const noexcept; + /// Mark packet with given sequence number as ack'd. + /// If all packets for the current chunk have been ack'd + /// the transmission is considered complete. + void Ack(std::uint16_t); + /// Mark packet with given sequence number as lost. + /// Its part of the chunk data should be resent. + void Nack(std::uint16_t); + + /// Cancel the current transmission. + void Abort(); + /// Start transmitting given chunk. + /// If there's a chunk already in transmission it will be + /// cancelled. + void Send(Chunk &); + +private: + void SendBegin(); + void SendData(std::size_t); + void Release(); + +private: + ClientConnection &conn; + Chunk *current; + std::size_t buffer_size; + std::unique_ptr buffer; + std::size_t buffer_len; + std::size_t packet_len; + std::size_t cursor; + std::size_t num_packets; + int begin_packet; + std::vector data_packets; + int confirm_wait; + std::uint32_t trans_id; + bool compressed; + +}; + +} + +#endif diff --git a/src/net/ClientConnection.hpp b/src/net/ClientConnection.hpp index f344efd..075fdce 100644 --- a/src/net/ClientConnection.hpp +++ b/src/net/ClientConnection.hpp @@ -1,18 +1,21 @@ #ifndef BLANK_NET_CLIENTCONNECTION_HPP_ #define BLANK_NET_CLIENTCONNECTION_HPP_ +#include "ChunkTransmitter.hpp" #include "Connection.hpp" #include "ConnectionHandler.hpp" +#include "Server.hpp" #include "../app/IntervalTimer.hpp" #include "../world/EntityState.hpp" +#include "../world/Player.hpp" +#include #include #include namespace blank { -class Entity; class Server; class ClientConnection @@ -29,11 +32,23 @@ public: Connection &GetConnection() noexcept { return conn; } bool Disconnected() const noexcept { return conn.Closed(); } - void AttachPlayer(Entity &); + /// prepare a packet of given type + template + Type Prepare() const noexcept { + return Packet::Make(server.GetPacket()); + } + /// send the previously prepared packet + std::uint16_t Send(); + /// send the previously prepared packet of non-default length + std::uint16_t Send(std::size_t len); + + void AttachPlayer(const Player &); void DetachPlayer(); - bool HasPlayer() const noexcept { return player; } - Entity &Player() noexcept { return *player; } - const Entity &Player() const noexcept { return *player; } + 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 { @@ -65,16 +80,23 @@ private: void CheckPlayerFix(); + void CheckChunkQueue(); + private: Server &server; Connection conn; - Entity *player; + Player player; std::list spawns; unsigned int confirm_wait; + EntityState player_update_state; std::uint16_t player_update_pack; IntervalTimer player_update_timer; + ChunkTransmitter transmitter; + std::deque chunk_queue; + glm::ivec3 old_base; + }; } diff --git a/src/net/ConnectionHandler.hpp b/src/net/ConnectionHandler.hpp index d0172fd..6e18da2 100644 --- a/src/net/ConnectionHandler.hpp +++ b/src/net/ConnectionHandler.hpp @@ -30,6 +30,8 @@ private: virtual void On(const Packet::DespawnEntity &) { } virtual void On(const Packet::EntityUpdate &) { } virtual void On(const Packet::PlayerCorrection &) { } + virtual void On(const Packet::ChunkBegin &) { } + virtual void On(const Packet::ChunkData &) { } }; diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 87decec..ba07858 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace blank { @@ -149,6 +150,35 @@ struct Packet { void ReadPlayerState(EntityState &) const noexcept; }; + struct ChunkBegin : public Payload { + static constexpr std::uint8_t TYPE = 9; + static constexpr std::size_t MAX_LEN = 24; + + void WriteTransmissionId(std::uint32_t) noexcept; + void ReadTransmissionId(std::uint32_t &) const noexcept; + void WriteFlags(std::uint32_t) noexcept; + void ReadFlags(std::uint32_t &) const noexcept; + void WriteChunkCoords(const glm::ivec3 &) noexcept; + void ReadChunkCoords(glm::ivec3 &) const noexcept; + void WriteDataSize(std::uint32_t) noexcept; + void ReadDataSize(std::uint32_t &) const noexcept; + }; + + struct ChunkData : public Payload { + static constexpr std::uint8_t TYPE = 10; + static constexpr std::size_t MAX_LEN = MAX_PAYLOAD_LEN; + static constexpr std::size_t MAX_DATA_LEN = MAX_LEN - 12; + + void WriteTransmissionId(std::uint32_t) noexcept; + void ReadTransmissionId(std::uint32_t &) const noexcept; + void WriteDataOffset(std::uint32_t) noexcept; + void ReadDataOffset(std::uint32_t &) const noexcept; + void WriteDataSize(std::uint32_t) noexcept; + void ReadDataSize(std::uint32_t &) const noexcept; + void WriteData(const std::uint8_t *, std::size_t len) noexcept; + void ReadData(std::uint8_t *, std::size_t maxlen) const noexcept; + }; + template PayloadType As() { diff --git a/src/net/chunk.cpp b/src/net/chunk.cpp new file mode 100644 index 0000000..96e5ce6 --- /dev/null +++ b/src/net/chunk.cpp @@ -0,0 +1,308 @@ +#include "ChunkReceiver.hpp" +#include "ChunkTransmission.hpp" +#include "ChunkTransmitter.hpp" + +#include "ClientConnection.hpp" +#include "Packet.hpp" +#include "../world/Chunk.hpp" +#include "../world/ChunkStore.hpp" + +#include +#include +#include + +using namespace std; + + +namespace blank { + +ChunkReceiver::ChunkReceiver(ChunkStore &store) +: store(store) +, transmissions() +, timer(5000) { + timer.Start(); +} + +ChunkReceiver::~ChunkReceiver() { + +} + +void ChunkReceiver::Update(int dt) { + timer.Update(dt); + for (ChunkTransmission &trans : transmissions) { + if (trans.active && (timer.Elapsed() - trans.last_update) > timer.Interval()) { + cout << "timeout for transmission of chunk " << trans.coords << endl; + trans.Clear(); + } + } + if (transmissions.size() > 3) { + for (auto iter = transmissions.begin(), end = transmissions.end(); iter != end; ++iter) { + if (!iter->active) { + transmissions.erase(iter); + break; + } + } + } +} + +void ChunkReceiver::Handle(const Packet::ChunkBegin &pack) { + uint32_t id; + pack.ReadTransmissionId(id); + ChunkTransmission &trans = GetTransmission(id); + pack.ReadFlags(trans.flags); + pack.ReadChunkCoords(trans.coords); + pack.ReadDataSize(trans.data_size); + trans.last_update = timer.Elapsed(); + trans.header_received = true; + Commit(trans); +} + +void ChunkReceiver::Handle(const Packet::ChunkData &pack) { + uint32_t id, pos, size; + pack.ReadTransmissionId(id); + pack.ReadDataOffset(pos); + if (pos >= sizeof(ChunkTransmission::buffer)) { + cout << "received chunk data offset outside of buffer size" << endl; + return; + } + pack.ReadDataSize(size); + ChunkTransmission &trans = GetTransmission(id); + size_t len = min(size_t(size), sizeof(ChunkTransmission::buffer) - pos); + pack.ReadData(&trans.buffer[pos], len); + // TODO: this method breaks when a packet arrives twice + trans.data_received += len; + trans.last_update = timer.Elapsed(); + Commit(trans); +} + +ChunkTransmission &ChunkReceiver::GetTransmission(uint32_t id) { + // search for ongoing + for (ChunkTransmission &trans : transmissions) { + if (trans.active && trans.id == id) { + return trans; + } + } + // search for unused + for (ChunkTransmission &trans : transmissions) { + if (!trans.active) { + trans.active = true; + trans.id = id; + return trans; + } + } + // allocate new + transmissions.emplace_back(); + transmissions.back().active = true; + transmissions.back().id = id; + return transmissions.back(); +} + +void ChunkReceiver::Commit(ChunkTransmission &trans) { + if (!trans.Complete()) return; + + Chunk *chunk = store.Allocate(trans.coords); + if (!chunk) { + // chunk no longer of interes, just drop the data + // it should probably be cached to disk, but not now :P + trans.Clear(); + return; + } + + const Byte *src = &trans.buffer[0]; + uLong src_len = min(size_t(trans.data_size), sizeof(ChunkTransmission::buffer)); + Byte *dst = reinterpret_cast(chunk->BlockData()); + uLong dst_len = Chunk::BlockSize(); + + if (trans.Compressed()) { + if (uncompress(dst, &dst_len, src, src_len) != Z_OK) { + // omg, now what? + cout << "got corruped chunk data for " << trans.coords << endl; + } + } else { + memcpy(dst, src, min(src_len, dst_len)); + } + trans.Clear(); +} + +ChunkTransmission::ChunkTransmission() +: id(0) +, flags(0) +, coords() +, data_size(0) +, data_received(0) +, last_update(0) +, header_received(false) +, active(false) +, buffer() { + +} + +void ChunkTransmission::Clear() noexcept { + data_size = 0; + data_received = 0; + last_update = 0; + header_received = false; + active = false; +} + +bool ChunkTransmission::Complete() const noexcept { + return header_received && data_received == data_size; +} + +bool ChunkTransmission::Compressed() const noexcept { + return flags & 1; +} + + +ChunkTransmitter::ChunkTransmitter(ClientConnection &conn) +: conn(conn) +, current(nullptr) +, buffer_size(Chunk::BlockSize() + 10) +, buffer(new uint8_t[buffer_size]) +, buffer_len(0) +, packet_len(Packet::ChunkData::MAX_DATA_LEN) +, cursor(0) +, num_packets(0) +, begin_packet(-1) +, data_packets() +, confirm_wait(0) +, trans_id(0) +, compressed(false) { + +} + +ChunkTransmitter::~ChunkTransmitter() { + Abort(); +} + +bool ChunkTransmitter::Idle() const noexcept { + return !Transmitting() && !Waiting(); +} + +bool ChunkTransmitter::Transmitting() const noexcept { + return cursor < num_packets; +} + +void ChunkTransmitter::Transmit() { + if (cursor < num_packets) { + SendData(cursor); + ++cursor; + } +} + +bool ChunkTransmitter::Waiting() const noexcept { + return confirm_wait > 0; +} + +void ChunkTransmitter::Ack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + begin_packet = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + for (int i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + data_packets[i] = -1; + --confirm_wait; + if (Idle()) { + Release(); + } + return; + } + } +} + +void ChunkTransmitter::Nack(uint16_t seq) { + if (!Waiting()) { + return; + } + if (seq == begin_packet) { + SendBegin(); + return; + } + for (size_t i = 0, end = data_packets.size(); i < end; ++i) { + if (seq == data_packets[i]) { + SendData(i); + return; + } + } +} + +void ChunkTransmitter::Abort() { + if (!current) return; + + Release(); + + begin_packet = -1; + data_packets.clear(); + confirm_wait = 0; +} + +void ChunkTransmitter::Send(Chunk &chunk) { + // abort current chunk, if any + Abort(); + + current = &chunk; + current->Ref(); + + // load new chunk data + compressed = true; + buffer_len = buffer_size; + if (compress(buffer.get(), &buffer_len, reinterpret_cast(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) { + // compression failed, send it uncompressed + buffer_len = Chunk::BlockSize(); + memcpy(buffer.get(), chunk.BlockData(), buffer_len); + compressed = false; + } + cursor = 0; + num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0); + data_packets.resize(num_packets, -1); + + ++trans_id; + SendBegin(); +} + +void ChunkTransmitter::SendBegin() { + uint32_t flags = compressed; + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteFlags(flags); + pack.WriteChunkCoords(current->Position()); + pack.WriteDataSize(buffer_len); + if (begin_packet == -1) { + ++confirm_wait; + } + begin_packet = conn.Send(); +} + +void ChunkTransmitter::SendData(size_t i) { + int pos = i * packet_len; + int len = min(packet_len, buffer_len - pos); + const uint8_t *data = &buffer[pos]; + + auto pack = conn.Prepare(); + pack.WriteTransmissionId(trans_id); + pack.WriteDataOffset(pos); + pack.WriteDataSize(len); + pack.WriteData(data, len); + + if (data_packets[i] == -1) { + ++confirm_wait; + } + data_packets[i] = conn.Send(); +} + +void ChunkTransmitter::Release() { + if (current) { + current->UnRef(); + current = nullptr; + } +} + +} diff --git a/src/net/net.cpp b/src/net/net.cpp index 4ad663e..f59b809 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -8,6 +8,7 @@ #include "../app/init.hpp" #include "../model/CompositeModel.hpp" +#include "../world/ChunkIndex.hpp" #include "../world/Entity.hpp" #include "../world/EntityState.hpp" #include "../world/World.hpp" @@ -30,6 +31,8 @@ constexpr size_t Packet::SpawnEntity::MAX_LEN; constexpr size_t Packet::DespawnEntity::MAX_LEN; constexpr size_t Packet::EntityUpdate::MAX_LEN; constexpr size_t Packet::PlayerCorrection::MAX_LEN; +constexpr size_t Packet::ChunkBegin::MAX_LEN; +constexpr size_t Packet::ChunkData::MAX_LEN; namespace { @@ -125,12 +128,15 @@ uint16_t Client::SendPart() { ClientConnection::ClientConnection(Server &server, const IPaddress &addr) : server(server) , conn(addr) -, player(nullptr) +, player(nullptr, nullptr) , spawns() , confirm_wait(0) , player_update_state() , player_update_pack(0) -, player_update_timer(1500) { +, player_update_timer(1500) +, transmitter(*this) +, chunk_queue() +, old_base() { conn.SetHandler(this); } @@ -191,6 +197,7 @@ void ClientConnection::Update(int dt) { } CheckPlayerFix(); + CheckChunkQueue(); } if (conn.ShouldPing()) { conn.SendPing(server.GetPacket(), server.GetSocket()); @@ -210,24 +217,33 @@ ClientConnection::SpawnStatus::~SpawnStatus() { bool ClientConnection::CanSpawn(const Entity &e) const noexcept { return - &e != player && + &e != player.entity && !e.Dead() && - manhattan_radius(e.ChunkCoords() - Player().ChunkCoords()) < 7; + manhattan_radius(e.ChunkCoords() - PlayerEntity().ChunkCoords()) < 7; } bool ClientConnection::CanDespawn(const Entity &e) const noexcept { return e.Dead() || - manhattan_radius(e.ChunkCoords() - Player().ChunkCoords()) > 7; + 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 = Packet::Make(server.GetPacket()); + auto pack = Prepare(); pack.WriteEntity(*status.entity); - status.spawn_pack = conn.Send(server.GetPacket(), server.GetSocket()); + status.spawn_pack = Send(); ++confirm_wait; } @@ -235,9 +251,9 @@ void ClientConnection::SendDespawn(SpawnStatus &status) { // don't double despawn if (status.despawn_pack != -1) return; - auto pack = Packet::Make(server.GetPacket()); + auto pack = Prepare(); pack.WriteEntityID(status.entity->ID()); - status.despawn_pack = conn.Send(server.GetPacket(), server.GetSocket()); + status.despawn_pack = Send(); ++confirm_wait; } @@ -246,16 +262,15 @@ void ClientConnection::SendUpdate(SpawnStatus &status) { if (status.spawn_pack != -1 || status.despawn_pack != -1) return; // TODO: pack entity updates - auto pack = Packet::Make(server.GetPacket()); + auto pack = Prepare(); pack.WriteEntityCount(1); pack.WriteEntity(*status.entity, 0); - server.GetPacket().len = Packet::EntityUpdate::GetSize(1); - conn.Send(server.GetPacket(), server.GetSocket()); + 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(Player().GetState()); + 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 @@ -263,29 +278,86 @@ void ClientConnection::CheckPlayerFix() { constexpr float fix_thresh = 0.0001f; if (dist_squared > fix_thresh) { - auto pack = Packet::Make(server.GetPacket()); + auto pack = Prepare(); pack.WritePacketSeq(player_update_pack); - pack.WritePlayer(Player()); - conn.Send(server.GetPacket(), server.GetSocket()); + pack.WritePlayer(PlayerEntity()); + Send(); } } -void ClientConnection::AttachPlayer(Entity &new_player) { +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->Ref(); - cout << "player \"" << player->Name() << "\" joined" << endl; + 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 (!player) return; - player->Kill(); - player->UnRef(); - cout << "player \"" << player->Name() << "\" left" << endl; - player = nullptr; + 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) { @@ -302,6 +374,9 @@ void ClientConnection::OnPacketReceived(uint16_t seq) { } 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) { @@ -323,26 +398,26 @@ void ClientConnection::On(const Packet::Login &pack) { string name; pack.ReadPlayerName(name); - Entity *new_player = server.GetWorld().AddPlayer(name).entity; + Player new_player = server.GetWorld().AddPlayer(name); - if (new_player) { + if (new_player.entity) { // success! - AttachPlayer(*new_player); + AttachPlayer(new_player); cout << "accepted login from player \"" << name << '"' << endl; - auto response = Packet::Make(server.GetPacket()); - response.WritePlayer(*new_player); + auto response = Prepare(); + response.WritePlayer(*new_player.entity); response.WriteWorldName(server.GetWorld().Name()); - conn.Send(server.GetPacket(), server.GetSocket()); + Send(); // set up update tracking - player_update_state = new_player->GetState(); + 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; - Packet::Make(server.GetPacket()); - conn.Send(server.GetPacket(), server.GetSocket()); + Prepare(); + Send(); conn.Close(); } } @@ -360,8 +435,8 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) { player_update_pack = pack.Seq(); pack.ReadPlayerState(player_update_state); // accept velocity and orientation as "user input" - Player().Velocity(player_update_state.velocity); - Player().Orientation(player_update_state.orient); + PlayerEntity().Velocity(player_update_state.velocity); + PlayerEntity().Orientation(player_update_state.orient); } } @@ -517,6 +592,10 @@ const char *Packet::Type2String(uint8_t t) noexcept { return "EntityUpdate"; case PlayerCorrection::TYPE: return "PlayerCorrection"; + case ChunkBegin::TYPE: + return "ChunkBegin"; + case ChunkData::TYPE: + return "ChunkData"; default: return "Unknown"; } @@ -688,6 +767,72 @@ void Packet::PlayerCorrection::ReadPlayerState(EntityState &state) const noexcep Read(state, 2); } +void Packet::ChunkBegin::WriteTransmissionId(uint32_t id) noexcept { + Write(id, 0); +} + +void Packet::ChunkBegin::ReadTransmissionId(uint32_t &id) const noexcept { + Read(id, 0); +} + +void Packet::ChunkBegin::WriteFlags(uint32_t f) noexcept { + Write(f, 4); +} + +void Packet::ChunkBegin::ReadFlags(uint32_t &f) const noexcept { + Read(f, 4); +} + +void Packet::ChunkBegin::WriteChunkCoords(const glm::ivec3 &pos) noexcept { + Write(pos, 8); +} + +void Packet::ChunkBegin::ReadChunkCoords(glm::ivec3 &pos) const noexcept { + Read(pos, 8); +} + +void Packet::ChunkBegin::WriteDataSize(uint32_t s) noexcept { + Write(s, 20); +} + +void Packet::ChunkBegin::ReadDataSize(uint32_t &s) const noexcept { + Read(s, 20); +} + +void Packet::ChunkData::WriteTransmissionId(uint32_t id) noexcept { + Write(id, 0); +} + +void Packet::ChunkData::ReadTransmissionId(uint32_t &id) const noexcept { + Read(id, 0); +} + +void Packet::ChunkData::WriteDataOffset(uint32_t o) noexcept { + Write(o, 4); +} + +void Packet::ChunkData::ReadDataOffset(uint32_t &o) const noexcept { + Read(o, 4); +} + +void Packet::ChunkData::WriteDataSize(uint32_t s) noexcept { + Write(s, 8); +} + +void Packet::ChunkData::ReadDataSize(uint32_t &s) const noexcept { + Read(s, 8); +} + +void Packet::ChunkData::WriteData(const uint8_t *d, size_t l) noexcept { + size_t len = min(length - 12, l); + memcpy(&data[12], d, len); +} + +void Packet::ChunkData::ReadData(uint8_t *d, size_t l) const noexcept { + size_t len = min(length - 12, l); + memcpy(d, &data[12], len); +} + void ConnectionHandler::Handle(const UDPpacket &udp_pack) { const Packet &pack = *reinterpret_cast(udp_pack.data); @@ -719,6 +864,12 @@ void ConnectionHandler::Handle(const UDPpacket &udp_pack) { case Packet::PlayerCorrection::TYPE: On(Packet::As(udp_pack)); break; + case Packet::ChunkBegin::TYPE: + On(Packet::As(udp_pack)); + break; + case Packet::ChunkData::TYPE: + On(Packet::As(udp_pack)); + break; default: // drop unknown or unhandled packets break; diff --git a/src/world/ChunkIndex.hpp b/src/world/ChunkIndex.hpp index 6749737..17b9dd6 100644 --- a/src/world/ChunkIndex.hpp +++ b/src/world/ChunkIndex.hpp @@ -30,6 +30,11 @@ public: Chunk *operator [](int i) noexcept { return chunks[i]; } const Chunk *operator [](int i) const noexcept { return chunks[i]; } + int Extent() const noexcept { return extent; } + + Chunk::Pos CoordsBegin() const noexcept { return base - Chunk::Pos(extent); } + Chunk::Pos CoordsEnd() const noexcept { return base + Chunk::Pos(extent + 1); } + void Register(Chunk &) noexcept; int TotalChunks() const noexcept { return total_length; }