2 Commits

Author SHA1 Message Date
2b8447766a fix gcc build 2025-07-30 17:56:13 +02:00
2e556e0d45 feat: fast forward 2025-07-30 17:52:54 +02:00
12 changed files with 168 additions and 31 deletions

View File

@@ -48,6 +48,10 @@ class Array {
return m_Data[a_Index]; return m_Data[a_Index];
} }
const T& operator[](std::size_t a_Index) const {
return m_Data[a_Index];
}
~Array() { ~Array() {
delete [] m_Data; delete [] m_Data;
} }

View File

@@ -80,7 +80,29 @@ public:
std::uint8_t GetPlayerCount() const; std::uint8_t GetPlayerCount() const;
}; };
typedef std::array<Team, 2> TeamList; struct TeamList {
std::array<Team, 2> m_Teams;
TeamList() : m_Teams{Team{TeamColor::Red}, Team{TeamColor::Blue}}{
}
Team& operator[](std::size_t a_Index) {
return m_Teams[a_Index];
}
Team& operator[](TeamColor a_Index) {
return m_Teams[static_cast<std::size_t>(a_Index)];
}
const Team& operator[](std::size_t a_Index) const {
return m_Teams[a_Index];
}
const Team& operator[](TeamColor a_Index) const {
return m_Teams[static_cast<std::size_t>(a_Index)];
}
};
} // namespace game } // namespace game
} // namespace td } // namespace td

View File

@@ -107,15 +107,15 @@ class ConcreteTower : public sp::ConcreteMessage<TowerData, Tower, Type, false>
public: public:
using HandlerType = typename sp::ConcreteMessage<TowerData, Tower, Type, false>::HandlerType; using HandlerType = typename sp::ConcreteMessage<TowerData, Tower, Type, false>::HandlerType;
virtual TowerSize GetSize() const { virtual TowerSize GetSize() const override {
return Size; return Size;
} }
virtual TowerType GetType() const { virtual TowerType GetType() const override {
return Type; return Type;
} }
virtual void Tick(std::uint64_t delta, World* world) {} virtual void Tick(std::uint64_t delta, World* world) override {}
virtual void Dispatch(HandlerType& handler) const override { virtual void Dispatch(HandlerType& handler) const override {
handler.Handle(*this); handler.Handle(*this);

View File

@@ -20,8 +20,8 @@ class World {
TilePalette m_TilePalette; TilePalette m_TilePalette;
sim::WorldSnapshot m_CurrentState; std::shared_ptr<sim::WorldSnapshot> m_CurrentState;
sim::WorldSnapshot m_NextState; std::shared_ptr<sim::WorldSnapshot> m_NextState;
private: private:
sim::WorldTicker m_Ticker; sim::WorldTicker m_Ticker;
@@ -82,52 +82,55 @@ class World {
} }
const MobList& GetMobList() const { const MobList& GetMobList() const {
return m_CurrentState.m_Mobs; return m_CurrentState->m_Mobs;
} }
MobList& GetMobList() { MobList& GetMobList() {
return m_CurrentState.m_Mobs; return m_CurrentState->m_Mobs;
} }
const Color* GetTileColor(const TilePtr& tile) const; const Color* GetTileColor(const TilePtr& tile) const;
Team& GetRedTeam() { Team& GetRedTeam() {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(TeamColor::Red)]; return m_CurrentState->m_Teams[TeamColor::Red];
} }
const Team& GetRedTeam() const { const Team& GetRedTeam() const {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(TeamColor::Red)]; return m_CurrentState->m_Teams[TeamColor::Red];
} }
Team& GetBlueTeam() { Team& GetBlueTeam() {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(TeamColor::Blue)]; return m_CurrentState->m_Teams[TeamColor::Blue];
} }
const Team& GetBlueTeam() const { const Team& GetBlueTeam() const {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(TeamColor::Red)]; return m_CurrentState->m_Teams[TeamColor::Red];
} }
Team& GetTeam(TeamColor team) { Team& GetTeam(TeamColor team) {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(team)]; return m_CurrentState->m_Teams[team];
} }
const Team& GetTeam(TeamColor team) const { const Team& GetTeam(TeamColor team) const {
return m_CurrentState.m_Teams[static_cast<std::uint8_t>(team)]; return m_CurrentState->m_Teams[team];
} }
const TeamList& GetTeams() const { const TeamList& GetTeams() const {
return m_CurrentState.m_Teams; return m_CurrentState->m_Teams;
} }
const TowerList& GetTowers() const { const TowerList& GetTowers() const {
return m_CurrentState.m_Towers; return m_CurrentState->m_Towers;
} }
TowerPtr GetTowerById(TowerID tower); TowerPtr GetTowerById(TowerID tower);
const Player* GetPlayerById(PlayerID id) const; const Player* GetPlayerById(PlayerID id) const;
void Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta); const std::shared_ptr<sim::WorldSnapshot>& Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta);
void ResetSnapshots(std::shared_ptr<sim::WorldSnapshot>& a_Current, std::shared_ptr<sim::WorldSnapshot>& a_Next);
private: private:
void TickMobs(std::uint64_t delta); void TickMobs(std::uint64_t delta);
void CleanDeadMobs(); void CleanDeadMobs();
}; };

View File

@@ -3,6 +3,7 @@
#include <string> #include <string>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include <SDL3/SDL_keycode.h>
#include <td/misc/Signal.h> #include <td/misc/Signal.h>
namespace td { namespace td {
@@ -10,6 +11,7 @@ namespace td {
class Display { class Display {
public: public:
utils::Signal<float> OnAspectRatioChange; utils::Signal<float> OnAspectRatioChange;
utils::Signal<SDL_Keycode> OnKeyDown;
Display(int a_Width, int a_Height, const std::string& a_Title); Display(int a_Width, int a_Height, const std::string& a_Title);
~Display(); ~Display();

View File

@@ -1,34 +1,61 @@
#pragma once #pragma once
#include <td/game/World.h> #include <td/game/World.h>
#include <optional>
namespace td { namespace td {
namespace sim { namespace sim {
using GameHistory = std::vector<td::protocol::LockStep>; using GameHistory = std::vector<td::protocol::LockStep>;
using GameBuffer = std::vector<std::optional<td::protocol::LockStep>>;
class RealTimeSimulation { class RealTimeSimulation {
private: private:
std::uint64_t m_StepTime; std::uint64_t m_StepTime;
game::World& m_World; game::World& m_World;
GameHistory 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; std::size_t m_CurrentStep;
std::shared_ptr<WorldSnapshot> m_LastSnapshot;
std::uint64_t m_LastValidStep;
static const protocol::LockStep EMPTY_LOCKSTEP;
public: public:
/** /**
* \brief Replay constructor
* \param a_StepTime in ms * \param a_StepTime in ms
*/ */
RealTimeSimulation(game::World& a_World, GameHistory&& a_History, std::uint64_t a_StepTime); RealTimeSimulation(game::World& a_World, const GameHistory& a_History, std::uint64_t a_StepTime);
/**
* \brief Live update constructor (continuous game updates)
* \param a_StepTime in ms
*/
RealTimeSimulation(game::World& a_World, std::uint64_t a_StepTime);
/** /**
* \return the progress [0-1] between two steps * \return the progress [0-1] between two steps
*/ */
float Update(); float Update();
void HandlePacket(const protocol::packets::LockStepsPacket& a_LockSteps);
void HandlePacket(const protocol::packets::PredictCommandPacket& a_Predict);
private: private:
void Step(); void Step();
/**
* \brief Ticks a_Count times
*/
void FastForward(std::size_t a_Count);
/**
* \brief Tries to recompute simulation if needed (for example in late command receival)
*/
void FastReplay();
}; };
} // namespace sim } // namespace sim

View File

@@ -103,7 +103,17 @@ int main(int argc, char** argv) {
cam.SetCamPos({77, 5, 13}); cam.SetCamPos({77, 5, 13});
cam.UpdatePerspective(display.GetAspectRatio()); cam.UpdatePerspective(display.GetAspectRatio());
td::sim::RealTimeSimulation simulation(w, std::move(gh), 500); td::sim::RealTimeSimulation simulation(w, 500);
display.OnKeyDown.Connect([&simulation](SDL_Keycode key){
if (key == SDLK_A) {
auto spawn = std::make_shared<td::protocol::commands::SpawnTroopCommand>(0, 0, td::Vec2fp{td::FpFloat(77), td::FpFloat(13)}, 0);
td::Array<td::protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps{};
steps[0].push_back(spawn);
td::protocol::packets::LockStepsPacket packet{0, steps};
simulation.HandlePacket(packet);
}
});
while (!display.IsCloseRequested()) { while (!display.IsCloseRequested()) {
display.PollEvents(); display.PollEvents();

View File

@@ -5,7 +5,7 @@
namespace td { namespace td {
namespace game { namespace game {
World::World() : m_CurrentState{.m_Teams{Team{TeamColor::Red}, Team{TeamColor::Blue}}}, m_NextState{.m_Teams{Team{TeamColor::Red}, Team{TeamColor::Blue}}} {} World::World() : m_CurrentState(std::make_shared<sim::WorldSnapshot>()), m_NextState(m_CurrentState) {}
const Color* World::GetTileColor(const TilePtr& tile) const { const Color* World::GetTileColor(const TilePtr& tile) const {
switch (tile->GetType()) { switch (tile->GetType()) {
@@ -55,9 +55,15 @@ bool World::LoadMap(const protocol::pdata::WorldData& a_WorldData) {
return true; return true;
} }
void World::Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta) { const std::shared_ptr<sim::WorldSnapshot>& World::Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta) {
m_CurrentState = m_NextState; m_CurrentState = m_NextState;
m_NextState = m_Ticker.NextStep(*this, m_NextState, a_LockStep, a_Delta); m_NextState = std::make_shared<sim::WorldSnapshot>(m_Ticker.NextStep(*this, *m_NextState, a_LockStep, a_Delta));
return m_CurrentState;
}
void World::ResetSnapshots(std::shared_ptr<sim::WorldSnapshot>& a_Current, std::shared_ptr<sim::WorldSnapshot>& a_Next) {
m_CurrentState = a_Current;
m_NextState = a_Next;
} }
} // namespace game } // namespace game

View File

@@ -119,6 +119,7 @@ void Display::PollEvents() {
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED: { case SDL_EVENT_WINDOW_CLOSE_REQUESTED: {
m_ShouldClose = true; m_ShouldClose = true;
break;
} }
case SDL_EVENT_WINDOW_RESIZED: { case SDL_EVENT_WINDOW_RESIZED: {
@@ -126,6 +127,13 @@ void Display::PollEvents() {
m_LastHeight = event.window.data2; m_LastHeight = event.window.data2;
m_AspectRatio = (float)m_LastWidth / m_LastHeight; m_AspectRatio = (float)m_LastWidth / m_LastHeight;
OnAspectRatioChange(m_AspectRatio); OnAspectRatioChange(m_AspectRatio);
break;
}
case SDL_EVENT_KEY_DOWN: {
if(!event.key.repeat)
OnKeyDown(event.key.key);
break;
} }
default: default:

View File

@@ -5,7 +5,9 @@ namespace sim {
CommandApply::CommandApply(const game::World& a_World, WorldSnapshot& a_Snapshot) : m_World(a_World), m_Snapshot(a_Snapshot) {} CommandApply::CommandApply(const game::World& a_World, WorldSnapshot& a_Snapshot) : m_World(a_World), m_Snapshot(a_Snapshot) {}
void CommandApply::Handle(const protocol::commands::EndCommand& a_End) {} void CommandApply::Handle(const protocol::commands::EndCommand& a_End) {
(void) m_World;
}
void CommandApply::Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) { void CommandApply::Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) {
static game::TowerFactory factory; static game::TowerFactory factory;

View File

@@ -25,14 +25,14 @@ bool GameHistory::HasNextStep() const {
return HasLockStep(m_Cursor); return HasLockStep(m_Cursor);
} }
void GameHistory::FromPacket(td::protocol::pdata::LockSteps&& a_Steps) { void GameHistory::FromPacket(protocol::pdata::LockSteps&& a_Steps) {
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) { for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
protocol::LockStep& step = a_Steps.m_LockSteps[i]; protocol::LockStep& step = a_Steps.m_LockSteps[i];
SetLockStep(i + a_Steps.m_FirstFrameNumber, std::move(step)); SetLockStep(i + a_Steps.m_FirstFrameNumber, std::move(step));
} }
} }
td::protocol::packets::LockStepsPacket GameHistory::ToPacket(HistorySizeType a_StartIndex) { protocol::packets::LockStepsPacket GameHistory::ToPacket(HistorySizeType a_StartIndex) {
Array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps; Array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps;
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) { for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
steps[i] = GetLockStep(a_StartIndex + i); steps[i] = GetLockStep(a_StartIndex + i);

View File

@@ -5,18 +5,37 @@
namespace td { namespace td {
namespace sim { namespace sim {
const protocol::LockStep RealTimeSimulation::EMPTY_LOCKSTEP;
std::uint64_t GetTime() { std::uint64_t GetTime() {
return static_cast<std::uint64_t>( return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count());
} }
RealTimeSimulation::RealTimeSimulation(game::World& a_World, GameHistory&& a_History, std::uint64_t a_StepTime) : RealTimeSimulation::RealTimeSimulation(game::World& a_World, const GameHistory& a_History, std::uint64_t a_StepTime) :
m_StepTime(a_StepTime), m_StepTime(a_StepTime),
m_World(a_World), m_World(a_World),
m_History(std::move(a_History)),
m_CurrentTime(0), m_CurrentTime(0),
m_LastTime(GetTime()), m_LastTime(GetTime()),
m_CurrentStep(0) { m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) {
m_History.reserve(a_History.size());
for (const auto& lockstep : a_History) {
m_History.emplace_back(lockstep);
}
Step();
}
RealTimeSimulation::RealTimeSimulation(game::World& a_World, std::uint64_t a_StepTime) :
m_StepTime(a_StepTime),
m_World(a_World),
m_History(std::numeric_limits<std::uint16_t>::max()),
m_CurrentTime(0),
m_LastTime(GetTime()),
m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) {
Step(); Step();
} }
@@ -28,13 +47,47 @@ float RealTimeSimulation::Update() {
Step(); Step();
m_CurrentTime -= m_StepTime; m_CurrentTime -= m_StepTime;
} }
return (float) m_CurrentTime / (float) m_StepTime; return (float)m_CurrentTime / (float)m_StepTime;
} }
void RealTimeSimulation::Step() { void RealTimeSimulation::Step() {
m_World.Tick(m_History[m_CurrentStep], FpFloat(m_StepTime) / FpFloat(1000)); const auto& step = m_History[m_CurrentStep];
if (step.has_value()) {
m_LastSnapshot = m_World.Tick(step.value(), FpFloat(m_StepTime) / FpFloat(1000));
m_LastValidStep = m_CurrentStep;
} else {
m_World.Tick(EMPTY_LOCKSTEP, FpFloat(m_StepTime) / FpFloat(1000));
}
m_CurrentStep++; m_CurrentStep++;
} }
void RealTimeSimulation::HandlePacket(const protocol::packets::LockStepsPacket& a_LockSteps) {
const auto& steps = a_LockSteps->m_LockSteps;
for (std::size_t i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
m_History[a_LockSteps->m_FirstFrameNumber + i] = steps[i];
}
FastReplay();
}
void RealTimeSimulation::HandlePacket(const protocol::packets::PredictCommandPacket& a_Predict) {}
void RealTimeSimulation::FastForward(std::size_t a_Count) {
for (std::size_t i = 0; i < a_Count; i++) {
Step();
}
}
void RealTimeSimulation::FastReplay() {
if (m_LastValidStep >= m_CurrentStep)
return;
m_World.ResetSnapshots(m_LastSnapshot, m_LastSnapshot);
const std::size_t stepCount = m_CurrentStep - m_LastValidStep;
m_CurrentStep = m_LastValidStep;
FastForward(stepCount);
}
} // namespace sim } // namespace sim
} // namespace td } // namespace td