diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index deefaba..3d36b2d 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -101,6 +101,8 @@ void game::Vehicle::Update() ProcessInput(); UpdateWheels(); + sync_current_ = 1 - sync_current_; + UpdateSyncState(); SendUpdateMsg(); } @@ -109,7 +111,12 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const net::ModelName name(model_name_); msg.Write(name); net::WriteRGB(msg, color_); // primary color - WriteState(msg); + + // write state against default + static const VehicleSyncState default_state; + size_t fields_pos = msg.Reserve(); + auto fields = WriteState(msg, default_state); + msg.WriteAt(fields_pos, fields); } void game::Vehicle::SetInput(VehicleInputType type, bool enable) @@ -120,6 +127,12 @@ void game::Vehicle::SetInput(VehicleInputType type, bool enable) 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) { 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_); - net::WriteTransform(msg, root_.local); + VehicleSyncState& state = sync_[sync_current_]; - // send wheel info - // msg.Write(static_cast(numwheels)); - msg.Write(steering_); + state.flags = flags_; + + 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) { auto& wheel = wheels_[i]; - msg.Write(wheel.z_offset); - msg.Write(wheel.speed); + state.wheels[i].z_offset.Encode(wheel.z_offset); + 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() { auto msg = BeginEntMsg(net::EMSG_UPDATE); - WriteState(msg); + auto fields_pos = msg.Reserve(); + auto fields = WriteState(msg, sync_[1 - sync_current_]); + + // TODO: allow this + // if (fields == 0) + // { + // DiscardMsg(); + // return; + // } + + msg.WriteAt(fields_pos, fields); } diff --git a/src/game/vehicle.hpp b/src/game/vehicle.hpp index 6ef8211..f0b8260 100644 --- a/src/game/vehicle.hpp +++ b/src/game/vehicle.hpp @@ -7,13 +7,11 @@ #include "collision/motionstate.hpp" #include "entity.hpp" #include "world.hpp" -#include "vehicleflags.hpp" +#include "vehicle_sync.hpp" namespace game { -static constexpr size_t MAX_WHEELS = 4; - struct VehicleWheelState { float rotation = 0.0f; // [rad] @@ -44,6 +42,7 @@ public: void SetInput(VehicleInputType type, bool enable); + glm::vec3 GetPosition() const; void SetPosition(const glm::vec3& pos); virtual ~Vehicle(); @@ -51,7 +50,8 @@ public: private: void ProcessInput(); void UpdateWheels(); - void WriteState(net::OutMessage& msg) const; + void UpdateSyncState(); + VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const; void SendUpdateMsg(); private: @@ -69,7 +69,9 @@ private: size_t num_wheels_ = 0; std::array wheels_; - VehicleFlags flags_; + VehicleFlags flags_ = VF_NONE; + VehicleSyncState sync_[2]; + size_t sync_current_ = 0; VehicleInputFlags in_ = 0; }; diff --git a/src/game/vehicle_sync.hpp b/src/game/vehicle_sync.hpp new file mode 100644 index 0000000..ff14018 --- /dev/null +++ b/src/game/vehicle_sync.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#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 \ No newline at end of file diff --git a/src/game/vehicleflags.hpp b/src/game/vehicleflags.hpp deleted file mode 100644 index e292011..0000000 --- a/src/game/vehicleflags.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace game -{ - -using VehicleFlags = uint8_t; - -enum VehicleFlag : VehicleFlags -{ - VF_NONE, - VF_ACCELERATING = 1, - VF_BREAKING = 2, -}; - -} \ No newline at end of file diff --git a/src/gameview/vehicleview.cpp b/src/gameview/vehicleview.cpp index 61d9245..2f4f41f 100644 --- a/src/gameview/vehicleview.cpp +++ b/src/gameview/vehicleview.cpp @@ -20,6 +20,11 @@ game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg) @@ -32,7 +37,9 @@ std::unique_ptr game::view::VehicleView::InitFromMsg(Wo auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); auto vehicle = std::make_unique(world, std::move(model), color); - vehicle->ReadState(msg); + if (!vehicle->ReadState(msg)) + return nullptr; + vehicle->root_trans_[0] = vehicle->root_trans_[1]; return vehicle; @@ -135,27 +142,73 @@ bool game::view::VehicleView::ReadState(net::InMessage& msg) auto& root_trans = root_trans_[1]; update_time_ = world_.GetTime(); - if (!msg.Read(flags_)) + // parse state delta + VehicleSyncFieldFlags fields; + if (!msg.Read(fields)) return false; - if (!net::ReadTransform(msg, root_trans)) - return false; + // flags + if (fields & VSF_FLAGS) + { + if (!msg.Read(flags_)) + return false; + } - float steering; - if (!msg.Read(steering)) - return false; + // pos + if (fields & VSF_POSITION) + { + 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(); for (size_t i = 0; i < wheels_.size(); ++i) { auto& wheel = wheels_[i]; - if (!msg.Read(wheel.z_offset) || !msg.Read(wheel.speed)) - return false; + wheel.z_offset = sync_.wheels[i].z_offset.Decode(); + wheel.speed = sync_.wheels[i].speed.Decode(); wheel.steering = i < 2 ? steering : 0.0f; } - return true;} + return true; +} bool game::view::VehicleView::ProcessUpdateMsg(net::InMessage& msg) { diff --git a/src/gameview/vehicleview.hpp b/src/gameview/vehicleview.hpp index 6e39cc0..e64b93f 100644 --- a/src/gameview/vehicleview.hpp +++ b/src/gameview/vehicleview.hpp @@ -3,7 +3,7 @@ #include "entityview.hpp" #include "assets/vehiclemdl.hpp" -#include "game/vehicleflags.hpp" +#include "game/vehicle_sync.hpp" #include @@ -38,6 +38,7 @@ private: std::shared_ptr model_; glm::vec4 color_; + game::VehicleSyncState sync_; std::vector wheels_; float update_time_ = 0.0f; diff --git a/src/net/defs.hpp b/src/net/defs.hpp index cd8b6d7..93df648 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -74,9 +74,19 @@ enum EntMsgType : uint8_t EMSG_UPDATE, }; -using PositionQ = Quantized; +using PositionElemQ = Quantized; +struct PositionQ +{ + PositionElemQ x, y, z; +}; + using AngleQ = Quantized; -using QuatQ = Quantized; + +using QuatElemQ = Quantized; +struct QuatQ +{ + QuatElemQ x, y, z; +}; using WheelZOffsetQ = Quantized; using RotationSpeedQ = Quantized; diff --git a/src/net/msg_producer.cpp b/src/net/msg_producer.cpp index aa0b16b..92d9358 100644 --- a/src/net/msg_producer.cpp +++ b/src/net/msg_producer.cpp @@ -9,8 +9,14 @@ void net::MsgProducer::ResetMsg() net::OutMessage net::MsgProducer::BeginMsg(net::MessageType type) { + msg_start_ = message_buf_.size(); OutMessage msg(message_buf_); if (type != net::MSG_NONE) msg.Write(type); return msg; } + +void net::MsgProducer::DiscardMsg() +{ + message_buf_.resize(msg_start_); +} diff --git a/src/net/msg_producer.hpp b/src/net/msg_producer.hpp index 4f9d2de..7eddece 100644 --- a/src/net/msg_producer.hpp +++ b/src/net/msg_producer.hpp @@ -15,10 +15,12 @@ public: void ResetMsg(); OutMessage BeginMsg(MessageType type = MSG_NONE); + void DiscardMsg(); std::span GetMsg() const { return message_buf_; }; private: std::vector message_buf_; + size_t msg_start_ = 0; }; } \ No newline at end of file diff --git a/src/net/outmessage.hpp b/src/net/outmessage.hpp index 29b49b4..6cd68e0 100644 --- a/src/net/outmessage.hpp +++ b/src/net/outmessage.hpp @@ -78,6 +78,12 @@ public: Write(q.value); } + // template + // void Write(T quant) + // { + // Write(quant.value); + // } + void WriteVarInt(int64_t value) { const bool negative = value < 0; diff --git a/src/net/quantized.hpp b/src/net/quantized.hpp index 01418a1..bb19e72 100644 --- a/src/net/quantized.hpp +++ b/src/net/quantized.hpp @@ -19,11 +19,11 @@ struct Quantized static constexpr float inv_scale = range / static_cast(max_int); public: - T value; + T value{0}; Quantized() = default; Quantized(T value) : value(value) {} - Quantized(float fvalue) { Encode(value); } + Quantized(float fvalue) { Encode(fvalue); } void Encode(float fvalue) noexcept { diff --git a/src/net/utils.hpp b/src/net/utils.hpp index 04bb61a..bfbf1c6 100644 --- a/src/net/utils.hpp +++ b/src/net/utils.hpp @@ -1,8 +1,8 @@ #pragma once #include "defs.hpp" -#include "outmessage.hpp" #include "inmessage.hpp" +#include "outmessage.hpp" #include "utils/transform.hpp" namespace net @@ -10,51 +10,107 @@ namespace net /// TRANSFORMS -inline void WritePosition(OutMessage& msg, const glm::vec3& pos) +// inline void WritePosition(OutMessage& msg, const glm::vec3& pos) +// { +// msg.Write(pos.x); +// msg.Write(pos.y); +// msg.Write(pos.z); +// } + +inline void EncodePosition(const glm::vec3& pos, PositionQ& out) { - msg.Write(pos.x); - msg.Write(pos.y); - msg.Write(pos.z); + out.x.Encode(pos.x); + out.y.Encode(pos.y); + 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(q.x); +// msg.Write(q.y); +// msg.Write(q.z); +// } + +inline void EncodeRotation(const glm::quat& quat, QuatQ& out) { auto q = glm::normalize(quat); if (q.w < 0.0f) q = -q; - msg.Write(q.x); - msg.Write(q.y); - msg.Write(q.z); + out.x.Encode(q.x); + out.y.Encode(q.y); + 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); - WriteRotation(msg, trans.rotation); + msg.Write(rotq.x.value); + 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(pos.x) && msg.Read(pos.y) && msg.Read(pos.z); +// } + +inline bool ReadPositionQ(InMessage& msg, PositionQ& posq) { - return msg.Read(pos.x) && msg.Read(pos.y) && msg.Read(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; - if (!msg.Read(v.x) || !msg.Read(v.y) || !msg.Read(v.z)) - return false; + out.x = posq.x.Decode(); + out.y = posq.y.Decode(); + out.z = posq.z.Decode(); +} +// inline bool ReadRotation(InMessage& msg, glm::quat& q) +// { +// glm::vec3 v; +// if (!msg.Read(v.x) || !msg.Read(v.y) || !msg.Read(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))); - q = glm::quat(w, v.x, v.y, v.z); - - return true; + out = glm::quat(w, v.x, v.y, v.z); } -inline bool ReadTransform(InMessage& msg, Transform& trans) -{ - return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation); -} +// inline bool ReadTransform(InMessage& msg, Transform& trans) +// { +// return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation); +// } /// COLOR @@ -70,4 +126,50 @@ inline bool ReadRGB(InMessage& msg, glm::vec3& color) return msg.Read(color.r) && msg.Read(color.g) && msg.Read(color.b); } -} \ No newline at end of file +// DELTA +template +inline void WriteDelta(OutMessage& msg, T previous, T current) +{ + static_assert(sizeof(T) <= 4); + + int64_t delta = static_cast(current) - static_cast(previous); + + constexpr int64_t wrap = 1LL << (sizeof(T) * 8); + delta = (delta + wrap / 2) % wrap - wrap / 2; + + msg.WriteVarInt(delta); +} + +template +inline void WriteDelta(OutMessage& msg, T current, T previous) +{ + WriteDelta(msg, previous.value, current.value); +} + +template +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((static_cast(previous) + static_cast(encoded)) & mask); + return true; +} + +template +inline bool ReadDelta(InMessage& msg, T& value) +{ + return ReadDelta(msg, value, value); +} + +template +inline bool ReadDelta(InMessage& msg, T& quant) +{ + return ReadDelta(msg, quant.value, quant.value); +} + +} // namespace net \ No newline at end of file