177 lines
4.7 KiB
C++
177 lines
4.7 KiB
C++
#pragma once
|
|
|
|
|
|
#include "Codec.hpp"
|
|
#include "GlmCodec.hpp"
|
|
#include "EnttCodec.hpp"
|
|
|
|
#include <entt/entt.hpp>
|
|
#include <glm/vec3.hpp>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
namespace tw::serial {
|
|
|
|
class WorldStateWriter {
|
|
BinaryWriter m_w;
|
|
|
|
// Offsets for length-prefix patching
|
|
std::size_t m_entity_count_offset{ 0 };
|
|
uint32_t m_entity_count{ 0 };
|
|
|
|
public:
|
|
explicit WorldStateWriter(BinaryBuffer& buf) noexcept : m_w(buf) {}
|
|
|
|
void begin(uint32_t frame_idx, uint32_t tick_idx, uint32_t message_type) noexcept {
|
|
m_entity_count = 0;
|
|
|
|
// message_type — lets the receiver dispatch without peeking further
|
|
m_w.encode<uint32_t>(message_type);
|
|
|
|
// sequence — this message is never a reply to a request
|
|
m_w.encode<uint32_t>(0);
|
|
|
|
// frame_idx
|
|
m_w.encode<uint32_t>(frame_idx);
|
|
|
|
// tick_idx — counts the states that went out, so the receiver can tell
|
|
// a late one from a new one. Says nothing about the frame answered.
|
|
m_w.encode<uint32_t>(tick_idx);
|
|
|
|
// entity_count placeholder — patched when end() is called
|
|
m_entity_count_offset = m_w.reserve_u32();
|
|
}
|
|
|
|
template<typename Range>
|
|
void write_spawns(const Range& spawns) noexcept {
|
|
auto count = static_cast<uint32_t>(std::size(spawns));
|
|
m_w.encode<uint32_t>(count);
|
|
for (const entt::entity e : spawns) {
|
|
m_w.encode<entt::entity>(e);
|
|
}
|
|
}
|
|
|
|
template<typename Range>
|
|
void write_despawns(const Range& despawns) noexcept {
|
|
auto count = static_cast<uint32_t>(std::size(despawns));
|
|
// m_w.encode<uint32_t>(count);
|
|
for (const entt::entity e : despawns) {
|
|
m_w.encode<entt::entity>(e);
|
|
}
|
|
}
|
|
|
|
void write_entity(uint32_t id, const glm::vec3& pos) noexcept {
|
|
m_w.write(id);
|
|
// Write x, y, z as 3 contiguous floats
|
|
m_w.write_bytes(&pos.x, 3 * sizeof(float));
|
|
++m_entity_count;
|
|
}
|
|
|
|
void write_entity(entt::entity entity, const glm::vec3& pos) noexcept {
|
|
write_entity(static_cast<uint32_t>(entity), pos);
|
|
}
|
|
|
|
void end() noexcept {
|
|
m_w.patch_u32(m_entity_count_offset, m_entity_count);
|
|
}
|
|
|
|
std::span<const std::byte> view() noexcept {
|
|
return m_w.buffer().view();
|
|
}
|
|
|
|
void reset() noexcept {
|
|
m_w.reset();
|
|
m_entity_count = 0;
|
|
}
|
|
};
|
|
|
|
struct WorldStateHeader {
|
|
uint32_t packet_type;
|
|
uint32_t frame_idx;
|
|
uint32_t tick_idx;
|
|
uint32_t entity_count;
|
|
};
|
|
|
|
struct EntityRecord {
|
|
uint32_t id;
|
|
glm::vec3 position;
|
|
};
|
|
|
|
class WorldStateReader {
|
|
BinaryReader m_r;
|
|
WorldStateHeader m_header{};
|
|
|
|
uint32_t m_spawn_count{ 0 };
|
|
uint32_t m_spawns_read{ 0 };
|
|
|
|
uint32_t m_despawn_count{ 0 };
|
|
uint32_t m_despawns_read{ 0 };
|
|
|
|
uint32_t m_entities_read{ 0 };
|
|
|
|
public:
|
|
explicit WorldStateReader(std::span<const std::byte> data) noexcept
|
|
: m_r(data) {}
|
|
|
|
/** Read the header. Must be called first. */
|
|
WorldStateHeader read_header() noexcept {
|
|
// m_header.packet_type = m_r.decode<uint32_t>();
|
|
m_header.frame_idx = m_r.decode<uint32_t>();
|
|
m_header.tick_idx = m_r.decode<uint32_t>();
|
|
m_header.entity_count = m_r.decode<uint32_t>();
|
|
|
|
// spawn count follows immediately
|
|
m_spawn_count = m_r.decode<uint32_t>();
|
|
return m_header;
|
|
}
|
|
|
|
/** Read the next spawn entity id. Returns 0 when exhausted. */
|
|
bool has_spawn() const noexcept { return m_spawns_read < m_spawn_count; }
|
|
|
|
uint32_t read_spawn() noexcept {
|
|
assert(has_spawn());
|
|
++m_spawns_read;
|
|
uint32_t id = m_r.decode<uint32_t>();
|
|
// if (!has_spawn()) {
|
|
// // transition to despawns
|
|
// m_despawn_count = m_r.decode<uint32_t>();
|
|
// m_phase = Phase::Despawns;
|
|
// }
|
|
return id;
|
|
}
|
|
|
|
/** Skip remaining spawns and enter despawn phase. */
|
|
void skip_spawns() noexcept {
|
|
while (has_spawn()) read_spawn();
|
|
}
|
|
|
|
bool has_despawn() const noexcept { return m_despawns_read < m_despawn_count; }
|
|
|
|
uint32_t read_despawn() noexcept {
|
|
assert(has_despawn());
|
|
++m_despawns_read;
|
|
uint32_t id = m_r.decode<uint32_t>();
|
|
return id;
|
|
}
|
|
|
|
void skip_despawns() noexcept {
|
|
while (has_despawn()) read_despawn();
|
|
}
|
|
|
|
bool has_entity() const noexcept {
|
|
return m_entities_read < m_header.entity_count;
|
|
}
|
|
|
|
EntityRecord read_entity() noexcept {
|
|
assert(has_entity());
|
|
EntityRecord rec;
|
|
rec.id = m_r.read<uint32_t>();
|
|
m_r.read_bytes(&rec.position.x, 3 * sizeof(float));
|
|
++m_entities_read;
|
|
return rec;
|
|
}
|
|
};
|
|
|
|
} // namespace tw::serial
|