]> git.localhorst.tv Git - blank.git/commitdiff
transmit chunks from server to client
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 17 Sep 2015 09:23:19 +0000 (11:23 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 17 Sep 2015 15:50:29 +0000 (17:50 +0200)
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

14 files changed:
TODO
doc/protocol
src/client/InteractiveState.hpp
src/client/MasterState.hpp
src/client/client.cpp
src/net/ChunkReceiver.hpp [new file with mode: 0644]
src/net/ChunkTransmission.hpp [new file with mode: 0644]
src/net/ChunkTransmitter.hpp [new file with mode: 0644]
src/net/ClientConnection.hpp
src/net/ConnectionHandler.hpp
src/net/Packet.hpp
src/net/chunk.cpp [new file with mode: 0644]
src/net/net.cpp
src/world/ChunkIndex.hpp

diff --git a/TODO b/TODO
index b77cdae4859fa7d4c4b23d1352824f98bfae72e4..4ae41cc5b0439b723a00ebe1f469f6f6692bb48f 100644 (file)
--- 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
 
index 749e0995c775aa490294caff56c7bda6f8aa202b..607bbb164fdcb588719f69f09fcd522311a51a4a 100644 (file)
@@ -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
index 09a9919a2e7b15c8c563a480bdae426420480c02..39b393a578032e3e4aac22cb64153d8fcefd911f 100644 (file)
@@ -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;
index aaea6a99b442aa152b209d0f6953261746025b50..ff1769fdecf6f4db622b3c5c6579be5222210c85 100644 (file)
@@ -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
index 72d860d97772e75706d8ae81e2f5df5ec90fa756..4be9dbe88430cc8a77f677db8243af5520c6ee54 100644 (file)
@@ -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 (file)
index 0000000..40acc2f
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLANK_NET_CHUNKRECEIVER_HPP_
+#define BLANK_NET_CHUNKRECEIVER_HPP_
+
+#include "Packet.hpp"
+#include "../app/IntervalTimer.hpp"
+
+#include <cstdint>
+#include <list>
+
+
+namespace blank {
+
+class ChunkStore;
+class ChunkTransmission;
+
+class ChunkReceiver {
+
+public:
+       explicit ChunkReceiver(ChunkStore &);
+       ~ChunkReceiver();
+
+       void Update(int dt);
+
+       void Handle(const Packet::ChunkBegin &);
+       void Handle(const Packet::ChunkData &);
+
+private:
+       ChunkTransmission &GetTransmission(std::uint32_t id);
+       void Commit(ChunkTransmission &);
+
+private:
+       ChunkStore &store;
+       std::list<ChunkTransmission> transmissions;
+       IntervalTimer timer;
+
+};
+
+}
+
+#endif
diff --git a/src/net/ChunkTransmission.hpp b/src/net/ChunkTransmission.hpp
new file mode 100644 (file)
index 0000000..90dd35e
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_
+#define BLANK_NET_CHUNKTRANSMISSION_HPP_
+
+#include "../world/Chunk.hpp"
+
+#include <cstdint>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+struct ChunkTransmission {
+
+       std::uint32_t id;
+       std::uint32_t flags;
+       glm::ivec3 coords;
+       std::uint32_t data_size;
+       std::uint32_t data_received;
+
+       int last_update;
+
+       bool header_received;
+       bool active;
+
+       std::uint8_t buffer[Chunk::BlockSize() + 10];
+
+
+       ChunkTransmission();
+
+       void Clear() noexcept;
+
+       bool Complete() const noexcept;
+
+       bool Compressed() const noexcept;
+
+};
+
+};
+
+#endif
diff --git a/src/net/ChunkTransmitter.hpp b/src/net/ChunkTransmitter.hpp
new file mode 100644 (file)
index 0000000..44f6bd8
--- /dev/null
@@ -0,0 +1,73 @@
+#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
index f344efdcf883d6aa277e97ae2327ed8b34f4e089..075fdcec190655e3aff8534b40a359c36db8e70e 100644 (file)
@@ -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 <deque>
 #include <list>
 #include <SDL_net.h>
 
 
 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<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 &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<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;
+
 };
 
 }
index d0172fd2f87eeb07c30c1a43334abea0f02a6ca8..6e18da2727821b754904a3ebcc39534714f3b881 100644 (file)
@@ -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 &) { }
 
 };
 
index 87dececf9957f7c12ef960d59d6b0b7086ba802a..ba078585c0c06dae4ed5ad488b9123dfd7ecbaef 100644 (file)
@@ -5,6 +5,7 @@
 #include <ostream>
 #include <string>
 #include <SDL_net.h>
+#include <glm/glm.hpp>
 
 
 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<class PayloadType>
        PayloadType As() {
diff --git a/src/net/chunk.cpp b/src/net/chunk.cpp
new file mode 100644 (file)
index 0000000..96e5ce6
--- /dev/null
@@ -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 <iostream>
+#include <zlib.h>
+#include <glm/gtx/io.hpp>
+
+using namespace std;
+
+
+namespace blank {
+
+ChunkReceiver::ChunkReceiver(ChunkStore &store)
+: store(store)
+, transmissions()
+, timer(5000) {
+       timer.Start();
+}
+
+ChunkReceiver::~ChunkReceiver() {
+
+}
+
+void ChunkReceiver::Update(int dt) {
+       timer.Update(dt);
+       for (ChunkTransmission &trans : transmissions) {
+               if (trans.active && (timer.Elapsed() - trans.last_update) > timer.Interval()) {
+                       cout << "timeout for transmission of chunk " << trans.coords << endl;
+                       trans.Clear();
+               }
+       }
+       if (transmissions.size() > 3) {
+               for (auto iter = transmissions.begin(), end = transmissions.end(); iter != end; ++iter) {
+                       if (!iter->active) {
+                               transmissions.erase(iter);
+                               break;
+                       }
+               }
+       }
+}
+
+void ChunkReceiver::Handle(const Packet::ChunkBegin &pack) {
+       uint32_t id;
+       pack.ReadTransmissionId(id);
+       ChunkTransmission &trans = GetTransmission(id);
+       pack.ReadFlags(trans.flags);
+       pack.ReadChunkCoords(trans.coords);
+       pack.ReadDataSize(trans.data_size);
+       trans.last_update = timer.Elapsed();
+       trans.header_received = true;
+       Commit(trans);
+}
+
+void ChunkReceiver::Handle(const Packet::ChunkData &pack) {
+       uint32_t id, pos, size;
+       pack.ReadTransmissionId(id);
+       pack.ReadDataOffset(pos);
+       if (pos >= sizeof(ChunkTransmission::buffer)) {
+               cout << "received chunk data offset outside of buffer size" << endl;
+               return;
+       }
+       pack.ReadDataSize(size);
+       ChunkTransmission &trans = GetTransmission(id);
+       size_t len = min(size_t(size), sizeof(ChunkTransmission::buffer) - pos);
+       pack.ReadData(&trans.buffer[pos], len);
+       // TODO: this method breaks when a packet arrives twice
+       trans.data_received += len;
+       trans.last_update = timer.Elapsed();
+       Commit(trans);
+}
+
+ChunkTransmission &ChunkReceiver::GetTransmission(uint32_t id) {
+       // search for ongoing
+       for (ChunkTransmission &trans : transmissions) {
+               if (trans.active && trans.id == id) {
+                       return trans;
+               }
+       }
+       // search for unused
+       for (ChunkTransmission &trans : transmissions) {
+               if (!trans.active) {
+                       trans.active = true;
+                       trans.id = id;
+                       return trans;
+               }
+       }
+       // allocate new
+       transmissions.emplace_back();
+       transmissions.back().active = true;
+       transmissions.back().id = id;
+       return transmissions.back();
+}
+
+void ChunkReceiver::Commit(ChunkTransmission &trans) {
+       if (!trans.Complete()) return;
+
+       Chunk *chunk = store.Allocate(trans.coords);
+       if (!chunk) {
+               // chunk no longer of interes, just drop the data
+               // it should probably be cached to disk, but not now :P
+               trans.Clear();
+               return;
+       }
+
+       const Byte *src = &trans.buffer[0];
+       uLong src_len = min(size_t(trans.data_size), sizeof(ChunkTransmission::buffer));
+       Byte *dst = reinterpret_cast<Byte *>(chunk->BlockData());
+       uLong dst_len = Chunk::BlockSize();
+
+       if (trans.Compressed()) {
+               if (uncompress(dst, &dst_len, src, src_len) != Z_OK) {
+                       // omg, now what?
+                       cout << "got corruped chunk data for " << trans.coords << endl;
+               }
+       } else {
+               memcpy(dst, src, min(src_len, dst_len));
+       }
+       trans.Clear();
+}
+
+ChunkTransmission::ChunkTransmission()
+: id(0)
+, flags(0)
+, coords()
+, data_size(0)
+, data_received(0)
+, last_update(0)
+, header_received(false)
+, active(false)
+, buffer() {
+
+}
+
+void ChunkTransmission::Clear() noexcept {
+       data_size = 0;
+       data_received = 0;
+       last_update = 0;
+       header_received = false;
+       active = false;
+}
+
+bool ChunkTransmission::Complete() const noexcept {
+       return header_received && data_received == data_size;
+}
+
+bool ChunkTransmission::Compressed() const noexcept {
+       return flags & 1;
+}
+
+
+ChunkTransmitter::ChunkTransmitter(ClientConnection &conn)
+: conn(conn)
+, current(nullptr)
+, buffer_size(Chunk::BlockSize() + 10)
+, buffer(new uint8_t[buffer_size])
+, buffer_len(0)
+, packet_len(Packet::ChunkData::MAX_DATA_LEN)
+, cursor(0)
+, num_packets(0)
+, begin_packet(-1)
+, data_packets()
+, confirm_wait(0)
+, trans_id(0)
+, compressed(false) {
+
+}
+
+ChunkTransmitter::~ChunkTransmitter() {
+       Abort();
+}
+
+bool ChunkTransmitter::Idle() const noexcept {
+       return !Transmitting() && !Waiting();
+}
+
+bool ChunkTransmitter::Transmitting() const noexcept {
+       return cursor < num_packets;
+}
+
+void ChunkTransmitter::Transmit() {
+       if (cursor < num_packets) {
+               SendData(cursor);
+               ++cursor;
+       }
+}
+
+bool ChunkTransmitter::Waiting() const noexcept {
+       return confirm_wait > 0;
+}
+
+void ChunkTransmitter::Ack(uint16_t seq) {
+       if (!Waiting()) {
+               return;
+       }
+       if (seq == begin_packet) {
+               begin_packet = -1;
+               --confirm_wait;
+               if (Idle()) {
+                       Release();
+               }
+               return;
+       }
+       for (int i = 0, end = data_packets.size(); i < end; ++i) {
+               if (seq == data_packets[i]) {
+                       data_packets[i] = -1;
+                       --confirm_wait;
+                       if (Idle()) {
+                               Release();
+                       }
+                       return;
+               }
+       }
+}
+
+void ChunkTransmitter::Nack(uint16_t seq) {
+       if (!Waiting()) {
+               return;
+       }
+       if (seq == begin_packet) {
+               SendBegin();
+               return;
+       }
+       for (size_t i = 0, end = data_packets.size(); i < end; ++i) {
+               if (seq == data_packets[i]) {
+                       SendData(i);
+                       return;
+               }
+       }
+}
+
+void ChunkTransmitter::Abort() {
+       if (!current) return;
+
+       Release();
+
+       begin_packet = -1;
+       data_packets.clear();
+       confirm_wait = 0;
+}
+
+void ChunkTransmitter::Send(Chunk &chunk) {
+       // abort current chunk, if any
+       Abort();
+
+       current = &chunk;
+       current->Ref();
+
+       // load new chunk data
+       compressed = true;
+       buffer_len = buffer_size;
+       if (compress(buffer.get(), &buffer_len, reinterpret_cast<const Bytef *>(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) {
+               // compression failed, send it uncompressed
+               buffer_len = Chunk::BlockSize();
+               memcpy(buffer.get(), chunk.BlockData(), buffer_len);
+               compressed = false;
+       }
+       cursor = 0;
+       num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0);
+       data_packets.resize(num_packets, -1);
+
+       ++trans_id;
+       SendBegin();
+}
+
+void ChunkTransmitter::SendBegin() {
+       uint32_t flags = compressed;
+       auto pack = conn.Prepare<Packet::ChunkBegin>();
+       pack.WriteTransmissionId(trans_id);
+       pack.WriteFlags(flags);
+       pack.WriteChunkCoords(current->Position());
+       pack.WriteDataSize(buffer_len);
+       if (begin_packet == -1) {
+               ++confirm_wait;
+       }
+       begin_packet = conn.Send();
+}
+
+void ChunkTransmitter::SendData(size_t i) {
+       int pos = i * packet_len;
+       int len = min(packet_len, buffer_len - pos);
+       const uint8_t *data = &buffer[pos];
+
+       auto pack = conn.Prepare<Packet::ChunkData>();
+       pack.WriteTransmissionId(trans_id);
+       pack.WriteDataOffset(pos);
+       pack.WriteDataSize(len);
+       pack.WriteData(data, len);
+
+       if (data_packets[i] == -1) {
+               ++confirm_wait;
+       }
+       data_packets[i] = conn.Send();
+}
+
+void ChunkTransmitter::Release() {
+       if (current) {
+               current->UnRef();
+               current = nullptr;
+       }
+}
+
+}
index 4ad663ec8d2b9d701efd62a4e6def3195346a7e5..f59b80962530e5a4f4ca8b883b454247eb466a86 100644 (file)
@@ -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<Packet::SpawnEntity>(server.GetPacket());
+       auto pack = Prepare<Packet::SpawnEntity>();
        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<Packet::DespawnEntity>(server.GetPacket());
+       auto pack = Prepare<Packet::DespawnEntity>();
        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<Packet::EntityUpdate>(server.GetPacket());
+       auto pack = Prepare<Packet::EntityUpdate>();
        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<Packet::PlayerCorrection>(server.GetPacket());
+               auto pack = Prepare<Packet::PlayerCorrection>();
                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<Packet::Join>(server.GetPacket());
-               response.WritePlayer(*new_player);
+               auto response = Prepare<Packet::Join>();
+               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<Packet::Part>(server.GetPacket());
-               conn.Send(server.GetPacket(), server.GetSocket());
+               Prepare<Packet::Part>();
+               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<const Packet *>(udp_pack.data);
@@ -719,6 +864,12 @@ void ConnectionHandler::Handle(const UDPpacket &udp_pack) {
                case Packet::PlayerCorrection::TYPE:
                        On(Packet::As<Packet::PlayerCorrection>(udp_pack));
                        break;
+               case Packet::ChunkBegin::TYPE:
+                       On(Packet::As<Packet::ChunkBegin>(udp_pack));
+                       break;
+               case Packet::ChunkData::TYPE:
+                       On(Packet::As<Packet::ChunkData>(udp_pack));
+                       break;
                default:
                        // drop unknown or unhandled packets
                        break;
index 6749737acb8f6966101aad7ba92a78f89d81ff35..17b9dd697de901f38b4febbb8e6ba4e40241c2d2 100644 (file)
@@ -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; }