fill client holes (lockstep)

This commit is contained in:
2025-08-07 10:53:00 +02:00
parent c813c49707
commit ba84864b6a
9 changed files with 95 additions and 22 deletions

View File

@@ -19,6 +19,7 @@ class GameStateHandler : public protocol::PacketHandler {
virtual void Handle(const protocol::packets::SpawnTroopPacket& a_Packet) override; virtual void Handle(const protocol::packets::SpawnTroopPacket& a_Packet) override;
virtual void Handle(const protocol::packets::PlaceTowerPacket& a_Packet) override; virtual void Handle(const protocol::packets::PlaceTowerPacket& a_Packet) override;
virtual void Handle(const protocol::packets::LockStepRequestPacket& a_Packet) override;
}; };
} // namespace server } // namespace server

View File

@@ -110,6 +110,14 @@ struct PlaceTower {
TowerCoords m_Position; TowerCoords m_Position;
}; };
struct LockStepRequest {
std::vector<StepTime> m_Missing;
};
struct LockStepResponse {
std::map<StepTime, LockStep> m_Steps;
};
} // namespace pdata } // namespace pdata
} // namespace protocol } // namespace protocol
} // namespace td } // namespace td

View File

@@ -22,6 +22,8 @@ enum class PacketID : std::uint8_t {
BeginGame, BeginGame,
Disconnect, Disconnect,
KeepAlive, KeepAlive,
LockStepRequest,
LockStepResponse,
LockSteps, LockSteps,
LoggingSuccess, LoggingSuccess,
PlaceTower, PlaceTower,
@@ -49,6 +51,8 @@ using BeginGamePacket = PacketMessage<pdata::BeginGame, PacketID::BeginGame>;
using ChatMessagePacket = PacketMessage<pdata::ChatMessage, PacketID::ChatMessage>; using ChatMessagePacket = PacketMessage<pdata::ChatMessage, PacketID::ChatMessage>;
using DisconnectPacket = PacketMessage<pdata::Disconnect, PacketID::Disconnect>; using DisconnectPacket = PacketMessage<pdata::Disconnect, PacketID::Disconnect>;
using KeepAlivePacket = PacketMessage<pdata::KeepAlive, PacketID::KeepAlive>; using KeepAlivePacket = PacketMessage<pdata::KeepAlive, PacketID::KeepAlive>;
using LockStepRequestPacket = PacketMessage<pdata::LockStepRequest, PacketID::LockStepRequest>;
using LockStepResponsePacket = PacketMessage<pdata::LockStepResponse, PacketID::LockStepResponse>;
using LockStepsPacket = PacketMessage<pdata::LockSteps, PacketID::LockSteps>; using LockStepsPacket = PacketMessage<pdata::LockSteps, PacketID::LockSteps>;
using LoggingSuccessPacket = PacketMessage<pdata::LoggingSuccess, PacketID::LoggingSuccess>; using LoggingSuccessPacket = PacketMessage<pdata::LoggingSuccess, PacketID::LoggingSuccess>;
using PlaceTowerPacket = PacketMessage<pdata::PlaceTower, PacketID::PlaceTower>; using PlaceTowerPacket = PacketMessage<pdata::PlaceTower, PacketID::PlaceTower>;
@@ -63,7 +67,7 @@ using WorldDataPacket = PacketMessage<pdata::WorldData, PacketID::WorldData>;
} // namespace packets } // namespace packets
using AllPackets = std::tuple<packets::BeginGamePacket, packets::ChatMessagePacket, packets::DisconnectPacket, using AllPackets = std::tuple<packets::BeginGamePacket, packets::ChatMessagePacket, packets::DisconnectPacket,
packets::KeepAlivePacket, packets::LockStepsPacket, packets::LoggingSuccessPacket, packets::PlaceTowerPacket, packets::KeepAlivePacket, packets::LockStepRequestPacket, packets::LockStepResponsePacket, packets::LockStepsPacket, packets::LoggingSuccessPacket, packets::PlaceTowerPacket,
packets::PlayerJoinPacket, packets::PlayerLeavePacket, packets::PlayerLoginPacket, packets::PredictCommandPacket, packets::PlayerJoinPacket, packets::PlayerLeavePacket, packets::PlayerLoginPacket, packets::PredictCommandPacket,
packets::SpawnTroopPacket, packets::WorldHeaderPacket, packets::WorldDataPacket>; packets::SpawnTroopPacket, packets::WorldHeaderPacket, packets::WorldDataPacket>;

View File

@@ -2,6 +2,7 @@
#include <td/game/World.h> #include <td/game/World.h>
#include <optional> #include <optional>
#include <td/misc/Signal.h>
namespace td { namespace td {
namespace sim { namespace sim {
@@ -17,16 +18,18 @@ class ClientSimulation : public protocol::PacketHandler {
GameBuffer m_History; GameBuffer m_History;
std::uint64_t m_CurrentTime; std::uint64_t m_CurrentTime;
std::uint64_t m_LastTime; std::uint64_t m_LastTime;
std::size_t m_CurrentStep; StepTime m_CurrentStep;
std::shared_ptr<WorldSnapshot> m_LastSnapshot; std::shared_ptr<WorldSnapshot> m_LastSnapshot;
std::uint64_t m_LastValidStep; StepTime m_LastValidStep;
static const protocol::LockStep EMPTY_LOCKSTEP; static const protocol::LockStep EMPTY_LOCKSTEP;
using protocol::PacketHandler::Handle; using protocol::PacketHandler::Handle;
public: public:
utils::Signal<const std::vector<StepTime>&> OnMissingLockSteps;
/** /**
* \brief Replay constructor * \brief Replay constructor
* \param a_StepTime in ms * \param a_StepTime in ms
@@ -45,15 +48,18 @@ class ClientSimulation : public protocol::PacketHandler {
float Update(); float Update();
virtual void Handle(const protocol::packets::LockStepsPacket& a_LockSteps) override; virtual void Handle(const protocol::packets::LockStepsPacket& a_LockSteps) override;
virtual void Handle(const protocol::packets::PredictCommandPacket& a_Predict) override; virtual void Handle(const protocol::packets::LockStepResponsePacket& a_LockSteps) override;
private: private:
void Step(); /**
* \returns false if the empty lockstep was used
*/
bool Step();
/** /**
* \brief Ticks a_Count times * \brief Ticks a_Count times
*/ */
void FastForward(std::size_t a_Count); std::size_t FastForward(std::size_t a_Count);
/** /**
* \brief Tries to recompute simulation if needed (for example in late command receival) * \brief Tries to recompute simulation if needed (for example in late command receival)

View File

@@ -31,6 +31,8 @@ class ServerSimulation : public protocol::CommandHandler {
virtual void Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) override; virtual void Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) override;
virtual void Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) override; virtual void Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) override;
protocol::packets::LockStepResponsePacket GetResponse(const protocol::packets::LockStepRequestPacket& a_Missing) const;
}; };
} // namespace sim } // namespace sim

View File

@@ -49,8 +49,8 @@ class ClientHandler : public td::protocol::PacketHandler {
m_Simulation.Handle(a_LockStep); m_Simulation.Handle(a_LockStep);
} }
void Handle(const td::protocol::packets::PredictCommandPacket& a_Predict) { void Handle(const td::protocol::packets::LockStepResponsePacket& a_LockStep) {
m_Simulation.Handle(a_Predict); m_Simulation.Handle(a_LockStep);
} }
}; };
@@ -125,6 +125,10 @@ int main(int argc, char** argv) {
td::sim::ClientSimulation simulation(*clientWorld, td::STEP_TIME); td::sim::ClientSimulation simulation(*clientWorld, td::STEP_TIME);
ClientHandler clientHandler(simulation); ClientHandler clientHandler(simulation);
simulation.OnMissingLockSteps.Connect([&fakeSocket](const std::vector<td::StepTime>& a_MissingSteps){
fakeSocket->OnReceive(0, td::protocol::packets::LockStepRequestPacket(a_MissingSteps));
});
// temporary tests // temporary tests
display.OnKeyDown.Connect([&fakeSocket](SDL_Keycode key) { display.OnKeyDown.Connect([&fakeSocket](SDL_Keycode key) {
if (key == SDLK_A) { if (key == SDLK_A) {
@@ -134,6 +138,7 @@ int main(int argc, char** argv) {
} }
}); });
// make a fake player join
fakeSocket->ConnectFakePeer(0); fakeSocket->ConnectFakePeer(0);
// packets from the server to the client // packets from the server to the client

View File

@@ -20,5 +20,9 @@ void GameStateHandler::Handle(const protocol::packets::PlaceTowerPacket& a_Packe
m_GameState.m_Simulation.Handle(place); m_GameState.m_Simulation.Handle(place);
} }
void GameStateHandler::Handle(const protocol::packets::LockStepRequestPacket& a_Packet) {
m_GameState.SendPacket(m_PlayerId, m_GameState.m_Simulation.GetResponse(a_Packet));
}
} // namespace server } // namespace server
} // namespace td } // namespace td

View File

@@ -4,6 +4,8 @@
#include <iostream> #include <iostream>
// TODO: mutex this bad boy
namespace td { namespace td {
namespace sim { namespace sim {
@@ -37,9 +39,7 @@ ClientSimulation::ClientSimulation(game::World& a_World, std::uint64_t a_StepTim
m_LastTime(GetTime()), m_LastTime(GetTime()),
m_CurrentStep(0), m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()), m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) { m_LastValidStep(0) {}
// Step();
}
float ClientSimulation::Update() { float ClientSimulation::Update() {
// TODO: handle freezes (m_CurrentTime > 2 * m_StepTime) // TODO: handle freezes (m_CurrentTime > 2 * m_StepTime)
@@ -52,15 +52,20 @@ float ClientSimulation::Update() {
return (float)m_CurrentTime / (float)m_StepTime; return (float)m_CurrentTime / (float)m_StepTime;
} }
void ClientSimulation::Step() { bool ClientSimulation::Step() {
const auto& step = m_History[m_CurrentStep]; const auto& step = m_History[m_CurrentStep];
if (step.has_value()) { if (step.has_value()) {
m_LastSnapshot = m_World.Tick(step.value(), FpFloat(m_StepTime) / FpFloat(1000)); auto snapshot = m_World.Tick(step.value(), FpFloat(m_StepTime) / FpFloat(1000));
m_LastValidStep = m_CurrentStep; if (m_LastValidStep + 1 == m_CurrentStep) {
m_LastValidStep = m_CurrentStep;
m_LastSnapshot = snapshot;
}
} else { } else {
m_World.Tick(EMPTY_LOCKSTEP, FpFloat(m_StepTime) / FpFloat(1000)); m_World.Tick(EMPTY_LOCKSTEP, FpFloat(m_StepTime) / FpFloat(1000));
std::cout << "Empty tick (" << m_CurrentStep << ") !\n";
} }
m_CurrentStep++; m_CurrentStep++;
return step.has_value();
} }
void ClientSimulation::Handle(const protocol::packets::LockStepsPacket& a_LockSteps) { void ClientSimulation::Handle(const protocol::packets::LockStepsPacket& a_LockSteps) {
@@ -68,16 +73,32 @@ void ClientSimulation::Handle(const protocol::packets::LockStepsPacket& a_LockSt
for (std::size_t i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) { for (std::size_t i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
m_History[a_LockSteps->m_FirstFrameNumber + i] = steps[i]; m_History[a_LockSteps->m_FirstFrameNumber + i] = steps[i];
} }
// to be in the state [0-1]
if (a_LockSteps->m_FirstFrameNumber == 0)
Step();
FastReplay(); FastReplay();
} }
void ClientSimulation::Handle(const protocol::packets::PredictCommandPacket& a_Predict) {} void ClientSimulation::Handle(const protocol::packets::LockStepResponsePacket& a_LockSteps) {
for (const auto& [stepTime, lockStep] : a_LockSteps->m_Steps) {
void ClientSimulation::FastForward(std::size_t a_Count) { m_History[stepTime] = lockStep;
for (std::size_t i = 0; i < a_Count; i++) {
Step();
} }
std::cout << "Was behind " << a_Count << " ticks !\n"; FastReplay();
}
std::size_t ClientSimulation::FastForward(std::size_t a_Count) {
std::size_t stepCount;
bool hasMissingStep = false;
for (std::size_t i = 0; i < a_Count; i++) {
if (!Step() && !hasMissingStep) {
hasMissingStep = true;
stepCount = i + 1;
}
}
std::cout << "Was behind " << (hasMissingStep ? stepCount : a_Count) << " ticks !\n";
return hasMissingStep ? stepCount : a_Count;
} }
void ClientSimulation::FastReplay() { void ClientSimulation::FastReplay() {
@@ -86,11 +107,21 @@ void ClientSimulation::FastReplay() {
m_World.ResetSnapshots(m_LastSnapshot, m_LastSnapshot); m_World.ResetSnapshots(m_LastSnapshot, m_LastSnapshot);
// TODO: cover holes
const std::size_t stepCount = m_CurrentStep - m_LastValidStep; const std::size_t stepCount = m_CurrentStep - m_LastValidStep;
m_CurrentStep = m_LastValidStep; m_CurrentStep = m_LastValidStep;
FastForward(stepCount); if (FastForward(stepCount) == stepCount)
return;
std::vector<StepTime> missingSteps;
missingSteps.reserve(stepCount);
for (StepTime step = m_LastValidStep + 1; step < m_CurrentStep; step++) {
if (!m_History[step].has_value())
missingSteps.push_back(step);
}
OnMissingLockSteps(missingSteps);
} }
} // namespace sim } // namespace sim

View File

@@ -36,5 +36,17 @@ void ServerSimulation::Handle(const protocol::commands::PlaceTowerCommand& a_Pla
AddToCommandHistory(m_History[m_CurrentTime + LOCKSTEP_BUFFER_SIZE], a_PlaceTower); AddToCommandHistory(m_History[m_CurrentTime + LOCKSTEP_BUFFER_SIZE], a_PlaceTower);
} }
protocol::packets::LockStepResponsePacket ServerSimulation::GetResponse(
const protocol::packets::LockStepRequestPacket& a_Missing) const {
std::map<StepTime, protocol::LockStep> result;
for (StepTime step : a_Missing->m_Missing) {
result.emplace(step, m_History[step]);
}
return {result};
}
} // namespace sim } // namespace sim
} // namespace td } // namespace td