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
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
#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"
World &GetWorld() noexcept { return world; }
Interface &GetInterface() noexcept { return interface; }
+ ChunkReceiver &GetChunkReceiver() noexcept { return chunk_receiver; }
Skeletons &GetSkeletons() noexcept { return skeletons; }
void OnEnter() override;
WorldSave save;
World world;
Interface interface;
+ ChunkReceiver chunk_receiver;
ChunkRenderer chunk_renderer;
Skeletons skeletons;
IntervalTimer loop_timer;
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
world,
world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
)
+, chunk_receiver(world.Chunks())
, chunk_renderer(*interface.GetPlayer().chunks)
, skeletons()
, loop_timer(16)
void InteractiveState::Update(int dt) {
loop_timer.Update(dt);
master.Update(dt);
+ chunk_receiver.Update(dt);
interface.Update(dt);
int world_dt = 0;
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);
+}
+
}
}
--- /dev/null
+#ifndef BLANK_NET_CHUNKRECEIVER_HPP_
+#define BLANK_NET_CHUNKRECEIVER_HPP_
+
+#include "Packet.hpp"
+#include "../app/IntervalTimer.hpp"
+
+#include <cstdint>
+#include <list>
+
+
+namespace blank {
+
+class ChunkStore;
+class ChunkTransmission;
+
+class ChunkReceiver {
+
+public:
+ explicit ChunkReceiver(ChunkStore &);
+ ~ChunkReceiver();
+
+ void Update(int dt);
+
+ void Handle(const Packet::ChunkBegin &);
+ void Handle(const Packet::ChunkData &);
+
+private:
+ ChunkTransmission &GetTransmission(std::uint32_t id);
+ void Commit(ChunkTransmission &);
+
+private:
+ ChunkStore &store;
+ std::list<ChunkTransmission> transmissions;
+ IntervalTimer timer;
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_NET_CHUNKTRANSMISSION_HPP_
+#define BLANK_NET_CHUNKTRANSMISSION_HPP_
+
+#include "../world/Chunk.hpp"
+
+#include <cstdint>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+struct ChunkTransmission {
+
+ std::uint32_t id;
+ std::uint32_t flags;
+ glm::ivec3 coords;
+ std::uint32_t data_size;
+ std::uint32_t data_received;
+
+ int last_update;
+
+ bool header_received;
+ bool active;
+
+ std::uint8_t buffer[Chunk::BlockSize() + 10];
+
+
+ ChunkTransmission();
+
+ void Clear() noexcept;
+
+ bool Complete() const noexcept;
+
+ bool Compressed() const noexcept;
+
+};
+
+};
+
+#endif
--- /dev/null
+#ifndef BLANK_NET_CHUNKTRANSMITTER_HPP_
+#define BLANK_NET_CHUNKTRANSMITTER_HPP_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+
+namespace blank {
+
+class Chunk;
+class ClientConnection;
+
+class ChunkTransmitter {
+
+public:
+ explicit ChunkTransmitter(ClientConnection &);
+ ~ChunkTransmitter();
+
+ /// Returns true if not transmitting or waiting on acks, so
+ /// the next chunk may be queued without schmutzing up anything.
+ bool Idle() const noexcept;
+
+ /// Returns true if a transmission is still going on,
+ /// meaning there's at least one packet that needs to
+ /// be sent.
+ bool Transmitting() const noexcept;
+ /// Send the next packet of the current chunk (if any).
+ void Transmit();
+
+ /// Returns true if there's one or more packets which
+ /// still have to be ack'd by the remote.
+ bool Waiting() const noexcept;
+ /// Mark packet with given sequence number as ack'd.
+ /// If all packets for the current chunk have been ack'd
+ /// the transmission is considered complete.
+ void Ack(std::uint16_t);
+ /// Mark packet with given sequence number as lost.
+ /// Its part of the chunk data should be resent.
+ void Nack(std::uint16_t);
+
+ /// Cancel the current transmission.
+ void Abort();
+ /// Start transmitting given chunk.
+ /// If there's a chunk already in transmission it will be
+ /// cancelled.
+ void Send(Chunk &);
+
+private:
+ void SendBegin();
+ void SendData(std::size_t);
+ void Release();
+
+private:
+ ClientConnection &conn;
+ Chunk *current;
+ std::size_t buffer_size;
+ std::unique_ptr<std::uint8_t[]> buffer;
+ std::size_t buffer_len;
+ std::size_t packet_len;
+ std::size_t cursor;
+ std::size_t num_packets;
+ int begin_packet;
+ std::vector<int> data_packets;
+ int confirm_wait;
+ std::uint32_t trans_id;
+ bool compressed;
+
+};
+
+}
+
+#endif
#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
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 {
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;
+
};
}
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 &) { }
};
#include <ostream>
#include <string>
#include <SDL_net.h>
+#include <glm/glm.hpp>
namespace blank {
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() {
--- /dev/null
+#include "ChunkReceiver.hpp"
+#include "ChunkTransmission.hpp"
+#include "ChunkTransmitter.hpp"
+
+#include "ClientConnection.hpp"
+#include "Packet.hpp"
+#include "../world/Chunk.hpp"
+#include "../world/ChunkStore.hpp"
+
+#include <iostream>
+#include <zlib.h>
+#include <glm/gtx/io.hpp>
+
+using namespace std;
+
+
+namespace blank {
+
+ChunkReceiver::ChunkReceiver(ChunkStore &store)
+: store(store)
+, transmissions()
+, timer(5000) {
+ timer.Start();
+}
+
+ChunkReceiver::~ChunkReceiver() {
+
+}
+
+void ChunkReceiver::Update(int dt) {
+ timer.Update(dt);
+ for (ChunkTransmission &trans : transmissions) {
+ if (trans.active && (timer.Elapsed() - trans.last_update) > timer.Interval()) {
+ cout << "timeout for transmission of chunk " << trans.coords << endl;
+ trans.Clear();
+ }
+ }
+ if (transmissions.size() > 3) {
+ for (auto iter = transmissions.begin(), end = transmissions.end(); iter != end; ++iter) {
+ if (!iter->active) {
+ transmissions.erase(iter);
+ break;
+ }
+ }
+ }
+}
+
+void ChunkReceiver::Handle(const Packet::ChunkBegin &pack) {
+ uint32_t id;
+ pack.ReadTransmissionId(id);
+ ChunkTransmission &trans = GetTransmission(id);
+ pack.ReadFlags(trans.flags);
+ pack.ReadChunkCoords(trans.coords);
+ pack.ReadDataSize(trans.data_size);
+ trans.last_update = timer.Elapsed();
+ trans.header_received = true;
+ Commit(trans);
+}
+
+void ChunkReceiver::Handle(const Packet::ChunkData &pack) {
+ uint32_t id, pos, size;
+ pack.ReadTransmissionId(id);
+ pack.ReadDataOffset(pos);
+ if (pos >= sizeof(ChunkTransmission::buffer)) {
+ cout << "received chunk data offset outside of buffer size" << endl;
+ return;
+ }
+ pack.ReadDataSize(size);
+ ChunkTransmission &trans = GetTransmission(id);
+ size_t len = min(size_t(size), sizeof(ChunkTransmission::buffer) - pos);
+ pack.ReadData(&trans.buffer[pos], len);
+ // TODO: this method breaks when a packet arrives twice
+ trans.data_received += len;
+ trans.last_update = timer.Elapsed();
+ Commit(trans);
+}
+
+ChunkTransmission &ChunkReceiver::GetTransmission(uint32_t id) {
+ // search for ongoing
+ for (ChunkTransmission &trans : transmissions) {
+ if (trans.active && trans.id == id) {
+ return trans;
+ }
+ }
+ // search for unused
+ for (ChunkTransmission &trans : transmissions) {
+ if (!trans.active) {
+ trans.active = true;
+ trans.id = id;
+ return trans;
+ }
+ }
+ // allocate new
+ transmissions.emplace_back();
+ transmissions.back().active = true;
+ transmissions.back().id = id;
+ return transmissions.back();
+}
+
+void ChunkReceiver::Commit(ChunkTransmission &trans) {
+ if (!trans.Complete()) return;
+
+ Chunk *chunk = store.Allocate(trans.coords);
+ if (!chunk) {
+ // chunk no longer of interes, just drop the data
+ // it should probably be cached to disk, but not now :P
+ trans.Clear();
+ return;
+ }
+
+ const Byte *src = &trans.buffer[0];
+ uLong src_len = min(size_t(trans.data_size), sizeof(ChunkTransmission::buffer));
+ Byte *dst = reinterpret_cast<Byte *>(chunk->BlockData());
+ uLong dst_len = Chunk::BlockSize();
+
+ if (trans.Compressed()) {
+ if (uncompress(dst, &dst_len, src, src_len) != Z_OK) {
+ // omg, now what?
+ cout << "got corruped chunk data for " << trans.coords << endl;
+ }
+ } else {
+ memcpy(dst, src, min(src_len, dst_len));
+ }
+ trans.Clear();
+}
+
+ChunkTransmission::ChunkTransmission()
+: id(0)
+, flags(0)
+, coords()
+, data_size(0)
+, data_received(0)
+, last_update(0)
+, header_received(false)
+, active(false)
+, buffer() {
+
+}
+
+void ChunkTransmission::Clear() noexcept {
+ data_size = 0;
+ data_received = 0;
+ last_update = 0;
+ header_received = false;
+ active = false;
+}
+
+bool ChunkTransmission::Complete() const noexcept {
+ return header_received && data_received == data_size;
+}
+
+bool ChunkTransmission::Compressed() const noexcept {
+ return flags & 1;
+}
+
+
+ChunkTransmitter::ChunkTransmitter(ClientConnection &conn)
+: conn(conn)
+, current(nullptr)
+, buffer_size(Chunk::BlockSize() + 10)
+, buffer(new uint8_t[buffer_size])
+, buffer_len(0)
+, packet_len(Packet::ChunkData::MAX_DATA_LEN)
+, cursor(0)
+, num_packets(0)
+, begin_packet(-1)
+, data_packets()
+, confirm_wait(0)
+, trans_id(0)
+, compressed(false) {
+
+}
+
+ChunkTransmitter::~ChunkTransmitter() {
+ Abort();
+}
+
+bool ChunkTransmitter::Idle() const noexcept {
+ return !Transmitting() && !Waiting();
+}
+
+bool ChunkTransmitter::Transmitting() const noexcept {
+ return cursor < num_packets;
+}
+
+void ChunkTransmitter::Transmit() {
+ if (cursor < num_packets) {
+ SendData(cursor);
+ ++cursor;
+ }
+}
+
+bool ChunkTransmitter::Waiting() const noexcept {
+ return confirm_wait > 0;
+}
+
+void ChunkTransmitter::Ack(uint16_t seq) {
+ if (!Waiting()) {
+ return;
+ }
+ if (seq == begin_packet) {
+ begin_packet = -1;
+ --confirm_wait;
+ if (Idle()) {
+ Release();
+ }
+ return;
+ }
+ for (int i = 0, end = data_packets.size(); i < end; ++i) {
+ if (seq == data_packets[i]) {
+ data_packets[i] = -1;
+ --confirm_wait;
+ if (Idle()) {
+ Release();
+ }
+ return;
+ }
+ }
+}
+
+void ChunkTransmitter::Nack(uint16_t seq) {
+ if (!Waiting()) {
+ return;
+ }
+ if (seq == begin_packet) {
+ SendBegin();
+ return;
+ }
+ for (size_t i = 0, end = data_packets.size(); i < end; ++i) {
+ if (seq == data_packets[i]) {
+ SendData(i);
+ return;
+ }
+ }
+}
+
+void ChunkTransmitter::Abort() {
+ if (!current) return;
+
+ Release();
+
+ begin_packet = -1;
+ data_packets.clear();
+ confirm_wait = 0;
+}
+
+void ChunkTransmitter::Send(Chunk &chunk) {
+ // abort current chunk, if any
+ Abort();
+
+ current = &chunk;
+ current->Ref();
+
+ // load new chunk data
+ compressed = true;
+ buffer_len = buffer_size;
+ if (compress(buffer.get(), &buffer_len, reinterpret_cast<const Bytef *>(chunk.BlockData()), Chunk::BlockSize()) != Z_OK) {
+ // compression failed, send it uncompressed
+ buffer_len = Chunk::BlockSize();
+ memcpy(buffer.get(), chunk.BlockData(), buffer_len);
+ compressed = false;
+ }
+ cursor = 0;
+ num_packets = (buffer_len / packet_len) + (buffer_len % packet_len != 0);
+ data_packets.resize(num_packets, -1);
+
+ ++trans_id;
+ SendBegin();
+}
+
+void ChunkTransmitter::SendBegin() {
+ uint32_t flags = compressed;
+ auto pack = conn.Prepare<Packet::ChunkBegin>();
+ pack.WriteTransmissionId(trans_id);
+ pack.WriteFlags(flags);
+ pack.WriteChunkCoords(current->Position());
+ pack.WriteDataSize(buffer_len);
+ if (begin_packet == -1) {
+ ++confirm_wait;
+ }
+ begin_packet = conn.Send();
+}
+
+void ChunkTransmitter::SendData(size_t i) {
+ int pos = i * packet_len;
+ int len = min(packet_len, buffer_len - pos);
+ const uint8_t *data = &buffer[pos];
+
+ auto pack = conn.Prepare<Packet::ChunkData>();
+ pack.WriteTransmissionId(trans_id);
+ pack.WriteDataOffset(pos);
+ pack.WriteDataSize(len);
+ pack.WriteData(data, len);
+
+ if (data_packets[i] == -1) {
+ ++confirm_wait;
+ }
+ data_packets[i] = conn.Send();
+}
+
+void ChunkTransmitter::Release() {
+ if (current) {
+ current->UnRef();
+ current = nullptr;
+ }
+}
+
+}
#include "../app/init.hpp"
#include "../model/CompositeModel.hpp"
+#include "../world/ChunkIndex.hpp"
#include "../world/Entity.hpp"
#include "../world/EntityState.hpp"
#include "../world/World.hpp"
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 {
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);
}
}
CheckPlayerFix();
+ CheckChunkQueue();
}
if (conn.ShouldPing()) {
conn.SendPing(server.GetPacket(), server.GetSocket());
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;
}
// 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;
}
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
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) {
}
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) {
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();
}
}
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);
}
}
return "EntityUpdate";
case PlayerCorrection::TYPE:
return "PlayerCorrection";
+ case ChunkBegin::TYPE:
+ return "ChunkBegin";
+ case ChunkData::TYPE:
+ return "ChunkData";
default:
return "Unknown";
}
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);
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;
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; }