#1 - quicr module
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#include "PlayerReconciler.hpp"
|
||||
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
#include "world/Transform.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
PlayerReconciler::PlayerReconciler(JoltPhysicsWorld* physics)
|
||||
: m_physics(physics), m_last_reconciled_ack(0), m_rollback_count(0),
|
||||
m_last_correction_distance(0.0f), m_last_replayed_frames(0), m_last_ack_frame(0)
|
||||
{
|
||||
for (auto& record : m_records) {
|
||||
record.frame = 0;
|
||||
record.valid = false;
|
||||
record.input = glm::vec3(0.0f);
|
||||
record.predicted_position = glm::vec3(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerReconciler::record_input(uint32_t frame, glm::vec3 input) {
|
||||
size_t idx = frame % RING_SIZE;
|
||||
m_records[idx].frame = frame;
|
||||
m_records[idx].valid = true;
|
||||
m_records[idx].input = input;
|
||||
}
|
||||
|
||||
void PlayerReconciler::record_prediction(uint32_t frame, glm::vec3 position) {
|
||||
size_t idx = frame % RING_SIZE;
|
||||
if (m_records[idx].frame == frame && m_records[idx].valid) {
|
||||
m_records[idx].predicted_position = position;
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerReconciler::reconcile(uint32_t ack_frame, glm::vec3 authoritative_position,
|
||||
entt::entity player, entt::registry* registry,
|
||||
uint32_t current_frame)
|
||||
{
|
||||
if (ack_frame == 0 || ack_frame <= m_last_reconciled_ack || ack_frame >= current_frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_last_reconciled_ack = ack_frame;
|
||||
m_last_ack_frame = ack_frame;
|
||||
|
||||
Record& record = m_records[ack_frame % RING_SIZE];
|
||||
const bool has_prediction = record.valid && record.frame == ack_frame;
|
||||
|
||||
// With a prediction to compare against, an answer that already matches costs
|
||||
// nothing further. This is the case almost every frame.
|
||||
if (has_prediction) {
|
||||
float distance = glm::distance(record.predicted_position, authoritative_position);
|
||||
m_last_correction_distance = distance;
|
||||
|
||||
if (distance < kPositionEpsilon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Restoring the frame the answer describes keeps everything the simulation
|
||||
// derived from it, so only the character has to be moved. Without a stored
|
||||
// frame there is nothing to restore and the answer is taken as it stands.
|
||||
const bool restored = has_prediction && m_physics->rollback(ack_frame);
|
||||
|
||||
place_character(player, registry, authoritative_position, !restored);
|
||||
replay_from(ack_frame, player, registry, current_frame);
|
||||
|
||||
m_last_replayed_frames = current_frame - 1 - ack_frame;
|
||||
m_rollback_count++;
|
||||
|
||||
spdlog::debug("Corrected at frame {}: distance {}, replayed {}, restored {}",
|
||||
ack_frame, m_last_correction_distance, m_last_replayed_frames, restored);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerReconciler::place_character(entt::entity player, entt::registry* registry,
|
||||
glm::vec3 position, bool clear_velocity) {
|
||||
CharacterBody* body = registry->try_get<CharacterBody>(player);
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
|
||||
body->m_character->SetPosition(JPH::RVec3(position.x, position.y, position.z));
|
||||
|
||||
if (clear_velocity) {
|
||||
body->m_character->SetLinearVelocity(JPH::Vec3::sZero());
|
||||
body->m_desired_velocity = JPH::Vec3::sZero();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerReconciler::replay_from(uint32_t from_frame, entt::entity player,
|
||||
entt::registry* registry, uint32_t current_frame) {
|
||||
for (uint32_t f = from_frame + 1; f < current_frame; ++f) {
|
||||
m_physics->step(f, tw::JoltPhysicsWorld::FIXED_DELTA_TIME, true);
|
||||
|
||||
Transform* transform = registry->try_get<Transform>(player);
|
||||
if (!transform) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Frames the ring never saw still need an entry, or the answer to them
|
||||
// arrives with nothing to compare against and forces another correction.
|
||||
Record& replayed = m_records[f % RING_SIZE];
|
||||
replayed.frame = f;
|
||||
replayed.valid = true;
|
||||
replayed.predicted_position = transform->position();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerReconciler::reset_at(uint32_t frame) {
|
||||
m_last_reconciled_ack = frame;
|
||||
|
||||
for (auto& record : m_records) {
|
||||
record.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user