Make update send only delta

This commit is contained in:
tovjemam 2026-01-20 15:56:14 +01:00
parent 6c5b5fbd59
commit 2f456fe8ba
12 changed files with 377 additions and 73 deletions

View File

@ -101,6 +101,8 @@ void game::Vehicle::Update()
ProcessInput(); ProcessInput();
UpdateWheels(); UpdateWheels();
sync_current_ = 1 - sync_current_;
UpdateSyncState();
SendUpdateMsg(); SendUpdateMsg();
} }
@ -109,7 +111,12 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
net::ModelName name(model_name_); net::ModelName name(model_name_);
msg.Write(name); msg.Write(name);
net::WriteRGB(msg, color_); // primary color net::WriteRGB(msg, color_); // primary color
WriteState(msg);
// write state against default
static const VehicleSyncState default_state;
size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
auto fields = WriteState(msg, default_state);
msg.WriteAt(fields_pos, fields);
} }
void game::Vehicle::SetInput(VehicleInputType type, bool enable) void game::Vehicle::SetInput(VehicleInputType type, bool enable)
@ -120,6 +127,12 @@ void game::Vehicle::SetInput(VehicleInputType type, bool enable)
in_ &= ~(1 << type); in_ &= ~(1 << type);
} }
glm::vec3 game::Vehicle::GetPosition() const
{
btVector3 pos = body_->getWorldTransform().getOrigin();
return glm::vec3(pos.x(), pos.y(), pos.z());
}
void game::Vehicle::SetPosition(const glm::vec3& pos) void game::Vehicle::SetPosition(const glm::vec3& pos)
{ {
auto t = body_->getWorldTransform(); auto t = body_->getWorldTransform();
@ -240,25 +253,102 @@ void game::Vehicle::UpdateWheels()
} }
} }
void game::Vehicle::WriteState(net::OutMessage& msg) const void game::Vehicle::UpdateSyncState()
{ {
msg.Write(flags_); VehicleSyncState& state = sync_[sync_current_];
net::WriteTransform(msg, root_.local);
// send wheel info state.flags = flags_;
// msg.Write<uint8_t>(static_cast<uint8_t>(numwheels));
msg.Write<net::AngleQ>(steering_); net::EncodePosition(root_.local.position, state.pos);
net::EncodeRotation(root_.local.rotation, state.rot);
state.steering.Encode(steering_);
for (size_t i = 0; i < num_wheels_; ++i) for (size_t i = 0; i < num_wheels_; ++i)
{ {
auto& wheel = wheels_[i]; auto& wheel = wheels_[i];
msg.Write<net::WheelZOffsetQ>(wheel.z_offset); state.wheels[i].z_offset.Encode(wheel.z_offset);
msg.Write<net::RotationSpeedQ>(wheel.speed); state.wheels[i].speed.Encode(wheel.speed);
} }
} }
game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, const VehicleSyncState& base) const
{
VehicleSyncFieldFlags fields = 0;
const VehicleSyncState& curr = sync_[sync_current_];
if (curr.flags != base.flags)
{
fields |= VSF_FLAGS;
msg.Write(curr.flags);
}
if (curr.pos.x.value != base.pos.x.value ||
curr.pos.y.value != base.pos.y.value ||
curr.pos.z.value != base.pos.z.value)
{
fields |= VSF_POSITION;
net::WriteDelta(msg, curr.pos.x, base.pos.x);
net::WriteDelta(msg, curr.pos.y, base.pos.y);
net::WriteDelta(msg, curr.pos.z, base.pos.z);
}
if (curr.rot.x.value != base.rot.x.value ||
curr.rot.y.value != base.rot.y.value ||
curr.rot.z.value != base.rot.z.value)
{
fields |= VSF_ROTATION;
net::WriteDelta(msg, curr.rot.x, base.rot.x);
net::WriteDelta(msg, curr.rot.y, base.rot.y);
net::WriteDelta(msg, curr.rot.z, base.rot.z);
}
if (curr.steering.value != base.steering.value)
{
fields |= VSF_STEERING;
net::WriteDelta(msg, curr.steering, base.steering);
}
bool wheels_changed = false;
for (size_t i = 0; i < num_wheels_; ++i)
{
if (curr.wheels[i].z_offset.value != base.wheels[i].z_offset.value ||
curr.wheels[i].speed.value != base.wheels[i].speed.value)
{
wheels_changed = true;
break;
}
}
if (wheels_changed)
{
fields |= VSF_WHEELS;
for (size_t i = 0; i < num_wheels_; ++i)
{
net::WriteDelta(msg, curr.wheels[i].z_offset, base.wheels[i].z_offset);
net::WriteDelta(msg, curr.wheels[i].speed, base.wheels[i].speed);
}
}
return fields;
}
void game::Vehicle::SendUpdateMsg() void game::Vehicle::SendUpdateMsg()
{ {
auto msg = BeginEntMsg(net::EMSG_UPDATE); auto msg = BeginEntMsg(net::EMSG_UPDATE);
WriteState(msg); auto fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
auto fields = WriteState(msg, sync_[1 - sync_current_]);
// TODO: allow this
// if (fields == 0)
// {
// DiscardMsg();
// return;
// }
msg.WriteAt(fields_pos, fields);
} }

View File

@ -7,13 +7,11 @@
#include "collision/motionstate.hpp" #include "collision/motionstate.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "world.hpp" #include "world.hpp"
#include "vehicleflags.hpp" #include "vehicle_sync.hpp"
namespace game namespace game
{ {
static constexpr size_t MAX_WHEELS = 4;
struct VehicleWheelState struct VehicleWheelState
{ {
float rotation = 0.0f; // [rad] float rotation = 0.0f; // [rad]
@ -44,6 +42,7 @@ public:
void SetInput(VehicleInputType type, bool enable); void SetInput(VehicleInputType type, bool enable);
glm::vec3 GetPosition() const;
void SetPosition(const glm::vec3& pos); void SetPosition(const glm::vec3& pos);
virtual ~Vehicle(); virtual ~Vehicle();
@ -51,7 +50,8 @@ public:
private: private:
void ProcessInput(); void ProcessInput();
void UpdateWheels(); void UpdateWheels();
void WriteState(net::OutMessage& msg) const; void UpdateSyncState();
VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const;
void SendUpdateMsg(); void SendUpdateMsg();
private: private:
@ -69,7 +69,9 @@ private:
size_t num_wheels_ = 0; size_t num_wheels_ = 0;
std::array<VehicleWheelState, MAX_WHEELS> wheels_; std::array<VehicleWheelState, MAX_WHEELS> wheels_;
VehicleFlags flags_; VehicleFlags flags_ = VF_NONE;
VehicleSyncState sync_[2];
size_t sync_current_ = 0;
VehicleInputFlags in_ = 0; VehicleInputFlags in_ = 0;
}; };

49
src/game/vehicle_sync.hpp Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <cstdint>
#include "net/defs.hpp"
namespace game
{
constexpr size_t MAX_WHEELS = 4;
using VehicleFlags = uint8_t;
enum VehicleFlag : VehicleFlags
{
VF_NONE,
VF_ACCELERATING = 1,
VF_BREAKING = 2,
};
struct VehicleSyncState
{
VehicleFlags flags = VF_NONE;
net::PositionQ pos;
net::QuatQ rot;
net::AngleQ steering;
struct
{
net::WheelZOffsetQ z_offset; // how much compressed
net::RotationSpeedQ speed; // rad/s
} wheels[MAX_WHEELS];
};
using VehicleSyncFieldFlags = uint8_t;
enum VehicleSyncFieldFlag
{
VSF_FLAGS = 0x01,
VSF_POSITION = 0x02,
VSF_ROTATION = 0x04,
VSF_STEERING = 0x08,
VSF_WHEELS = 0x10,
};
} // namespace game

View File

@ -1,17 +0,0 @@
#pragma once
#include <cstdint>
namespace game
{
using VehicleFlags = uint8_t;
enum VehicleFlag : VehicleFlags
{
VF_NONE,
VF_ACCELERATING = 1,
VF_BREAKING = 2,
};
}

View File

@ -20,6 +20,11 @@ game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr<const ass
color_ = glm::vec4(color, 1.0f); color_ = glm::vec4(color, 1.0f);
snd_accel_ = assets::CacheManager::GetSound("data/auto.snd"); snd_accel_ = assets::CacheManager::GetSound("data/auto.snd");
// sync state
net::DecodePosition(sync_.pos, root_.local.position);
net::DecodeRotation(sync_.rot, root_.local.rotation);
} }
std::unique_ptr<game::view::VehicleView> game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg) std::unique_ptr<game::view::VehicleView> game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg)
@ -32,7 +37,9 @@ std::unique_ptr<game::view::VehicleView> game::view::VehicleView::InitFromMsg(Wo
auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh");
auto vehicle = std::make_unique<VehicleView>(world, std::move(model), color); auto vehicle = std::make_unique<VehicleView>(world, std::move(model), color);
vehicle->ReadState(msg); if (!vehicle->ReadState(msg))
return nullptr;
vehicle->root_trans_[0] = vehicle->root_trans_[1]; vehicle->root_trans_[0] = vehicle->root_trans_[1];
return vehicle; return vehicle;
@ -135,27 +142,73 @@ bool game::view::VehicleView::ReadState(net::InMessage& msg)
auto& root_trans = root_trans_[1]; auto& root_trans = root_trans_[1];
update_time_ = world_.GetTime(); update_time_ = world_.GetTime();
if (!msg.Read(flags_)) // parse state delta
VehicleSyncFieldFlags fields;
if (!msg.Read(fields))
return false; return false;
if (!net::ReadTransform(msg, root_trans)) // flags
return false; if (fields & VSF_FLAGS)
{
if (!msg.Read(flags_))
return false;
}
float steering; // pos
if (!msg.Read<net::AngleQ>(steering)) if (fields & VSF_POSITION)
return false; {
if (!net::ReadDelta(msg, sync_.pos.x) ||
!net::ReadDelta(msg, sync_.pos.y) ||
!net::ReadDelta(msg, sync_.pos.z))
return false;
net::DecodePosition(sync_.pos, root_trans.position);
}
// rot
if (fields & VSF_ROTATION)
{
if (!net::ReadDelta(msg, sync_.rot.x) ||
!net::ReadDelta(msg, sync_.rot.y) ||
!net::ReadDelta(msg, sync_.rot.z))
return false;
net::DecodeRotation(sync_.rot, root_trans.rotation);
}
// steering
if (fields & VSF_STEERING)
{
if (!net::ReadDelta(msg, sync_.steering))
return false;
}
float steering = sync_.steering.Decode();
// wheels
if (fields & VSF_WHEELS)
{
for (size_t i = 0; i < wheels_.size(); ++i)
{
if (!net::ReadDelta(msg, sync_.wheels[i].z_offset) ||
!net::ReadDelta(msg, sync_.wheels[i].speed))
return false;
}
}
const auto& wheels = model_->GetWheels(); const auto& wheels = model_->GetWheels();
for (size_t i = 0; i < wheels_.size(); ++i) for (size_t i = 0; i < wheels_.size(); ++i)
{ {
auto& wheel = wheels_[i]; auto& wheel = wheels_[i];
if (!msg.Read<net::WheelZOffsetQ>(wheel.z_offset) || !msg.Read<net::RotationSpeedQ>(wheel.speed)) wheel.z_offset = sync_.wheels[i].z_offset.Decode();
return false; wheel.speed = sync_.wheels[i].speed.Decode();
wheel.steering = i < 2 ? steering : 0.0f; wheel.steering = i < 2 ? steering : 0.0f;
} }
return true;} return true;
}
bool game::view::VehicleView::ProcessUpdateMsg(net::InMessage& msg) bool game::view::VehicleView::ProcessUpdateMsg(net::InMessage& msg)
{ {

View File

@ -3,7 +3,7 @@
#include "entityview.hpp" #include "entityview.hpp"
#include "assets/vehiclemdl.hpp" #include "assets/vehiclemdl.hpp"
#include "game/vehicleflags.hpp" #include "game/vehicle_sync.hpp"
#include <chrono> #include <chrono>
@ -38,6 +38,7 @@ private:
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
glm::vec4 color_; glm::vec4 color_;
game::VehicleSyncState sync_;
std::vector<VehicleWheelViewInfo> wheels_; std::vector<VehicleWheelViewInfo> wheels_;
float update_time_ = 0.0f; float update_time_ = 0.0f;

View File

@ -74,9 +74,19 @@ enum EntMsgType : uint8_t
EMSG_UPDATE, EMSG_UPDATE,
}; };
using PositionQ = Quantized<uint32_t, -10000, 10000, 1>; using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
struct PositionQ
{
PositionElemQ x, y, z;
};
using AngleQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>; using AngleQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;
using QuatQ = Quantized<uint16_t, -1, 1, 1>;
using QuatElemQ = Quantized<uint16_t, -1, 1, 1>;
struct QuatQ
{
QuatElemQ x, y, z;
};
using WheelZOffsetQ = Quantized<uint8_t, -1, 1, 1>; using WheelZOffsetQ = Quantized<uint8_t, -1, 1, 1>;
using RotationSpeedQ = Quantized<uint16_t, -300, 300, 1>; using RotationSpeedQ = Quantized<uint16_t, -300, 300, 1>;

View File

@ -9,8 +9,14 @@ void net::MsgProducer::ResetMsg()
net::OutMessage net::MsgProducer::BeginMsg(net::MessageType type) net::OutMessage net::MsgProducer::BeginMsg(net::MessageType type)
{ {
msg_start_ = message_buf_.size();
OutMessage msg(message_buf_); OutMessage msg(message_buf_);
if (type != net::MSG_NONE) if (type != net::MSG_NONE)
msg.Write(type); msg.Write(type);
return msg; return msg;
} }
void net::MsgProducer::DiscardMsg()
{
message_buf_.resize(msg_start_);
}

View File

@ -15,10 +15,12 @@ public:
void ResetMsg(); void ResetMsg();
OutMessage BeginMsg(MessageType type = MSG_NONE); OutMessage BeginMsg(MessageType type = MSG_NONE);
void DiscardMsg();
std::span<const char> GetMsg() const { return message_buf_; }; std::span<const char> GetMsg() const { return message_buf_; };
private: private:
std::vector<char> message_buf_; std::vector<char> message_buf_;
size_t msg_start_ = 0;
}; };
} }

View File

@ -78,6 +78,12 @@ public:
Write(q.value); Write(q.value);
} }
// template <AnyQuantized T>
// void Write(T quant)
// {
// Write(quant.value);
// }
void WriteVarInt(int64_t value) void WriteVarInt(int64_t value)
{ {
const bool negative = value < 0; const bool negative = value < 0;

View File

@ -19,11 +19,11 @@ struct Quantized
static constexpr float inv_scale = range / static_cast<float>(max_int); static constexpr float inv_scale = range / static_cast<float>(max_int);
public: public:
T value; T value{0};
Quantized() = default; Quantized() = default;
Quantized(T value) : value(value) {} Quantized(T value) : value(value) {}
Quantized(float fvalue) { Encode(value); } Quantized(float fvalue) { Encode(fvalue); }
void Encode(float fvalue) noexcept void Encode(float fvalue) noexcept
{ {

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "defs.hpp" #include "defs.hpp"
#include "outmessage.hpp"
#include "inmessage.hpp" #include "inmessage.hpp"
#include "outmessage.hpp"
#include "utils/transform.hpp" #include "utils/transform.hpp"
namespace net namespace net
@ -10,51 +10,107 @@ namespace net
/// TRANSFORMS /// TRANSFORMS
inline void WritePosition(OutMessage& msg, const glm::vec3& pos) // inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
// {
// msg.Write<PositionQ>(pos.x);
// msg.Write<PositionQ>(pos.y);
// msg.Write<PositionQ>(pos.z);
// }
inline void EncodePosition(const glm::vec3& pos, PositionQ& out)
{ {
msg.Write<PositionQ>(pos.x); out.x.Encode(pos.x);
msg.Write<PositionQ>(pos.y); out.y.Encode(pos.y);
msg.Write<PositionQ>(pos.z); out.z.Encode(pos.z);
} }
inline void WriteRotation(OutMessage& msg, const glm::quat& quat) inline void WritePositionQ(OutMessage& msg, const PositionQ& posq)
{
msg.Write(posq.x.value);
msg.Write(posq.y.value);
msg.Write(posq.z.value);
}
// inline void WriteRotation(OutMessage& msg, const glm::quat& quat)
// {
// auto q = glm::normalize(quat);
// if (q.w < 0.0f)
// q = -q;
// msg.Write<QuatQ>(q.x);
// msg.Write<QuatQ>(q.y);
// msg.Write<QuatQ>(q.z);
// }
inline void EncodeRotation(const glm::quat& quat, QuatQ& out)
{ {
auto q = glm::normalize(quat); auto q = glm::normalize(quat);
if (q.w < 0.0f) if (q.w < 0.0f)
q = -q; q = -q;
msg.Write<QuatQ>(q.x); out.x.Encode(q.x);
msg.Write<QuatQ>(q.y); out.y.Encode(q.y);
msg.Write<QuatQ>(q.z); out.z.Encode(q.z);
} }
inline void WriteTransform(OutMessage& msg, const Transform& trans) inline void WriteRotationQ(OutMessage& msg, const QuatQ& rotq)
{ {
WritePosition(msg, trans.position); msg.Write(rotq.x.value);
WriteRotation(msg, trans.rotation); msg.Write(rotq.y.value);
msg.Write(rotq.z.value);
} }
inline bool ReadPosition(InMessage& msg, glm::vec3& pos) // inline void WriteTransform(OutMessage& msg, const Transform& trans)
// {
// WritePosition(msg, trans.position);
// WriteRotation(msg, trans.rotation);
// }
// inline bool ReadPosition(InMessage& msg, glm::vec3& pos)
// {
// return msg.Read<PositionQ>(pos.x) && msg.Read<PositionQ>(pos.y) && msg.Read<PositionQ>(pos.z);
// }
inline bool ReadPositionQ(InMessage& msg, PositionQ& posq)
{ {
return msg.Read<PositionQ>(pos.x) && msg.Read<PositionQ>(pos.y) && msg.Read<PositionQ>(pos.z); return msg.Read(posq.x.value) && msg.Read(posq.y.value) && msg.Read(posq.z.value);
} }
inline bool ReadRotation(InMessage& msg, glm::quat& q) inline void DecodePosition(const PositionQ& posq, glm::vec3& out)
{ {
glm::vec3 v; out.x = posq.x.Decode();
if (!msg.Read<QuatQ>(v.x) || !msg.Read<QuatQ>(v.y) || !msg.Read<QuatQ>(v.z)) out.y = posq.y.Decode();
return false; out.z = posq.z.Decode();
}
// inline bool ReadRotation(InMessage& msg, glm::quat& q)
// {
// glm::vec3 v;
// if (!msg.Read<QuatQ>(v.x) || !msg.Read<QuatQ>(v.y) || !msg.Read<QuatQ>(v.z))
// return false;
// float w = glm::sqrt(glm::max(0.0f, 1.0f - glm::dot(v, v)));
// q = glm::quat(w, v.x, v.y, v.z);
// return true;
// }
inline bool ReadRotationQ(InMessage& msg, QuatQ& rotq)
{
return msg.Read(rotq.x.value) && msg.Read(rotq.y.value) && msg.Read(rotq.z.value);
}
inline void DecodeRotation(const QuatQ& rotq, glm::quat& out)
{
glm::vec3 v(rotq.x.Decode(), rotq.y.Decode(), rotq.z.Decode());
float w = glm::sqrt(glm::max(0.0f, 1.0f - glm::dot(v, v))); float w = glm::sqrt(glm::max(0.0f, 1.0f - glm::dot(v, v)));
q = glm::quat(w, v.x, v.y, v.z); out = glm::quat(w, v.x, v.y, v.z);
return true;
} }
inline bool ReadTransform(InMessage& msg, Transform& trans) // inline bool ReadTransform(InMessage& msg, Transform& trans)
{ // {
return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation); // return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation);
} // }
/// COLOR /// COLOR
@ -70,4 +126,50 @@ inline bool ReadRGB(InMessage& msg, glm::vec3& color)
return msg.Read<ColorQ>(color.r) && msg.Read<ColorQ>(color.g) && msg.Read<ColorQ>(color.b); return msg.Read<ColorQ>(color.r) && msg.Read<ColorQ>(color.g) && msg.Read<ColorQ>(color.b);
} }
// DELTA
template <std::unsigned_integral T>
inline void WriteDelta(OutMessage& msg, T previous, T current)
{
static_assert(sizeof(T) <= 4);
int64_t delta = static_cast<int64_t>(current) - static_cast<int64_t>(previous);
constexpr int64_t wrap = 1LL << (sizeof(T) * 8);
delta = (delta + wrap / 2) % wrap - wrap / 2;
msg.WriteVarInt(delta);
} }
template <AnyQuantized T>
inline void WriteDelta(OutMessage& msg, T current, T previous)
{
WriteDelta(msg, previous.value, current.value);
}
template <std::unsigned_integral T>
inline bool ReadDelta(InMessage& msg, T previous, T& current)
{
static_assert(sizeof(T) <= 4);
int64_t encoded;
if (!msg.ReadVarInt(encoded))
return false;
constexpr uint64_t mask = (1ULL << (sizeof(T) * 8)) - 1;
current = static_cast<T>((static_cast<uint64_t>(previous) + static_cast<uint64_t>(encoded)) & mask);
return true;
}
template <std::unsigned_integral T>
inline bool ReadDelta(InMessage& msg, T& value)
{
return ReadDelta<T>(msg, value, value);
}
template <AnyQuantized T>
inline bool ReadDelta(InMessage& msg, T& quant)
{
return ReadDelta(msg, quant.value, quant.value);
}
} // namespace net