begin client-server

This commit is contained in:
2025-08-06 20:10:56 +02:00
parent 89213e9a97
commit c813c49707
26 changed files with 264 additions and 188 deletions

View File

@@ -1,9 +1,9 @@
#include <iostream>
#include <td/protocol/packet/PacketSerialize.h>
#include <fstream>
#include <td/game/World.h>
#include <td/input/Display.h>
#include <td/protocol/packet/PacketSerialize.h>
#include <td/protocol/packet/Packets.h>
#include <td/render/renderer/EntityRenderer.h>
#include <td/render/renderer/TowerRenderer.h>
@@ -17,7 +17,7 @@
#include <server/Server.h>
#include <server/socket/FakeSocket.h>
#include <server/state/LobbyState.h>
#include <server/state/GameState.h>
class WorldApply : public td::protocol::PacketHandler {
private:
@@ -36,6 +36,24 @@ class WorldApply : public td::protocol::PacketHandler {
}
};
class ClientHandler : public td::protocol::PacketHandler {
private:
td::sim::ClientSimulation& m_Simulation;
using td::protocol::PacketHandler::Handle;
public:
ClientHandler(td::sim::ClientSimulation& a_Simulation) : m_Simulation(a_Simulation) {}
void Handle(const td::protocol::packets::LockStepsPacket& a_LockStep) {
m_Simulation.Handle(a_LockStep);
}
void Handle(const td::protocol::packets::PredictCommandPacket& a_Predict) {
m_Simulation.Handle(a_Predict);
}
};
void Save(const td::protocol::PacketBase& header, const td::protocol::PacketBase& data) {
auto comp = std::make_shared<sp::ZlibCompress>();
@@ -48,7 +66,7 @@ void Save(const td::protocol::PacketBase& header, const td::protocol::PacketBase
stream.WriteMessage(data, false);
}
td::game::World GetWorld() {
td::game::WorldPtr GetWorld() {
auto comp = std::make_shared<sp::ZlibCompress>();
std::ifstream fStream("test/tdmap.tdmap2");
@@ -59,8 +77,8 @@ td::game::World GetWorld() {
auto header = stream.ReadMessage(td::protocol::PacketID::WorldHeader);
auto data = stream.ReadMessage(td::protocol::PacketID::WorldData);
td::game::World w;
auto wa = std::make_shared<WorldApply>(w);
auto w = std::make_shared<td::game::World>();
auto wa = std::make_shared<WorldApply>(*w);
td::protocol::PacketDispatcher d;
d.RegisterHandler(wa);
@@ -80,24 +98,12 @@ void FastForward(td::game::World& a_World, const td::sim::GameHistory& a_LockSte
}
}
td::sim::GameHistory GetCustomHistory() {
constexpr std::size_t MAX_COUNT = 20 * 60 * 40;
td::sim::GameHistory gh(MAX_COUNT);
auto spawn = td::protocol::CommandPtr(
std::make_shared<td::protocol::commands::SpawnTroopCommand>(td::EntityType::Zombie, 0, td::Vec2fp{td::FpFloat(77), td::FpFloat(13)}, 0));
gh[0].push_back(spawn);
auto tower = td::protocol::CommandPtr(
std::make_shared<td::protocol::commands::PlaceTowerCommand>(td::TowerType::Archer, 0, td::TowerCoords{77, 13}));
gh[0].push_back(tower);
return gh;
}
int main(int argc, char** argv) {
td::game::World w = GetWorld();
td::game::WorldPtr serverWorld = GetWorld();
// server
auto fakeSocket = std::make_shared<td::server::FakeSocket>();
td::server::Server server(fakeSocket);
// init GL context
td::Display display(1920, 1080, "Tower-Defense 2");
@@ -106,42 +112,40 @@ int main(int argc, char** argv) {
display.OnAspectRatioChange.Connect([&cam](float a_AspectRatio) { cam.UpdatePerspective(a_AspectRatio); });
td::sim::GameHistory gh = GetCustomHistory();
td::game::WorldPtr clientWorld = GetWorld();
td::render::RenderPipeline renderer;
renderer.AddRenderer<td::render::WorldRenderer>(cam, w);
renderer.AddRenderer<td::render::EntityRenderer>(cam, w);
renderer.AddRenderer<td::render::TowerRenderer>(cam, w);
renderer.AddRenderer<td::render::WorldRenderer>(cam, *clientWorld);
renderer.AddRenderer<td::render::EntityRenderer>(cam, *clientWorld);
renderer.AddRenderer<td::render::TowerRenderer>(cam, *clientWorld);
cam.SetCamPos({77, 7, 13});
cam.UpdatePerspective(display.GetAspectRatio());
td::sim::ClientSimulation simulation(w, 50);
td::sim::ClientSimulation simulation(*clientWorld, td::STEP_TIME);
ClientHandler clientHandler(simulation);
display.OnKeyDown.Connect([&simulation](SDL_Keycode key) {
static int counter = 0;
// temporary tests
display.OnKeyDown.Connect([&fakeSocket](SDL_Keycode key) {
if (key == SDLK_A) {
auto spawn = td::protocol::CommandPtr(
std::make_shared<td::protocol::commands::SpawnTroopCommand>(td::EntityType::Zombie, 0, td::Vec2fp{td::FpFloat(77), td::FpFloat(13)}, 0));
std::array<td::protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps{};
steps[0].push_back(spawn);
td::protocol::packets::LockStepsPacket packet{counter * LOCKSTEP_BUFFER_SIZE * 3, steps};
simulation.Handle(packet);
counter++;
fakeSocket->OnReceive(0, td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1));
} else if (key == SDLK_Z) {
fakeSocket->OnReceive(0, td::protocol::packets::PlaceTowerPacket(td::TowerType::Archer, td::TowerCoords(77, 13)));
}
});
fakeSocket->ConnectFakePeer(0);
// server
auto socket = std::make_shared<td::server::FakeSocket>();
td::server::Server server(socket);
server.UpdateState(std::make_shared<td::server::LobbyState>());
server.Update(1.0f);
server.Update(1.0f);
socket->OnDisconnect(0);
// packets from the server to the client
fakeSocket->OnSend.Connect([&clientHandler](td::PeerID a_Peer, const td::protocol::PacketBase& a_Packet) {
a_Packet.Dispatch(clientHandler);
});
server.UpdateState(std::make_shared<td::server::GameState>(serverWorld));
while (!display.IsCloseRequested()) {
display.PollEvents();
server.Update();
float lerp = simulation.Update();
renderer.Render(lerp);
display.Update();

View File

@@ -21,5 +21,11 @@ void IServerSocket::Send(PlayerID a_PlayerId, const protocol::PacketBase& a_Pack
SendPeer(m_Ids.GetPeerId(a_PlayerId), a_Packet);
}
void IServerSocket::Broadcast(const protocol::PacketBase& a_Packet) {
for (auto [peerId, playerId] : m_Ids) {
SendPeer(peerId, a_Packet);
}
}
} // namespace server
} // namespace td

View File

@@ -10,6 +10,7 @@ void IServerState::SetServer(Server* a_Server) {
Connect(m_Server->m_Socket->OnConnect, std::bind(&IServerState::OnPlayerJoin, this, std::placeholders::_1));
Connect(m_Server->m_Socket->OnDisconnect, std::bind(&IServerState::OnPlayerLeave, this, std::placeholders::_1));
Connect(m_Server->m_Socket->OnReceive, std::bind(&IServerState::HandlePacket, this, std::placeholders::_1, std::placeholders::_2));
Init();
}
IServerState::IServerState() : m_Server(nullptr) {}
@@ -20,6 +21,10 @@ void IServerState::SendPacket(PlayerID a_Id, const protocol::PacketBase& a_Packe
m_Server->m_Socket->Send(a_Id, a_Packet);
}
void IServerState::BroadcastPacket(const protocol::PacketBase& a_Packet) {
m_Server->m_Socket->Broadcast(a_Packet);
}
void IServerState::SetNewState(const std::shared_ptr<IServerState>& a_NewState) {
m_Server->UpdateState(a_NewState);
}

View File

@@ -1 +1,14 @@
#include <server/Server.h>
#include <server/Server.h>
namespace td {
namespace server {
void Server::Update() {
auto timeElapsed = std::chrono::system_clock::now() - m_LastTime;
float timeSeconds = std::chrono::duration<float, std::chrono::seconds::period>(timeElapsed).count();
Update(timeSeconds);
m_LastTime = std::chrono::system_clock::now();
}
} // namespace server
} // namespace td

View File

@@ -7,5 +7,13 @@ void FakeSocket::SendPeer(PeerID a_Peer, const protocol::PacketBase& a_Packet) {
OnSend(a_Peer, a_Packet);
}
void FakeSocket::ConnectFakePeer(PeerID a_Peer) {
OnConnectPeer(a_Peer);
}
void FakeSocket::DisconnectFakePeer(PeerID a_Peer) {
OnDisconnectPeer(a_Peer);
}
} // namespace server
} // namespace td

View File

@@ -1,25 +1,36 @@
#include <server/state/GameState.h>
#include <server/state/GameStateHandler.h>
#include <iostream>
namespace td {
namespace server {
void GameState::HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) {
GameState::GameState(const std::shared_ptr<game::World>& a_World) : m_World(a_World), m_Simulation(*m_World, STEP_TIME), m_Time(0) {}
void GameState::Init() {
std::cout << "Switched to Game state !\n";
BroadcastPacket(m_Simulation.MakePacket());
}
void GameState::HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) {
GameStateHandler handler(*this, a_Id);
a_Packet.Dispatch(handler);
}
void GameState::Update(float a_Delta) {
static const float stepTimeSecond = static_cast<float>(STEP_TIME) / 1000.0f;
m_Time += a_Delta;
if (m_Time > stepTimeSecond) {
m_Time = std::fmod(m_Time, stepTimeSecond);
auto lockStepPacket = m_Simulation.Update();
BroadcastPacket(lockStepPacket);
}
}
void GameState::OnPlayerJoin(PlayerID a_Id) {
void GameState::OnPlayerJoin(PlayerID a_Id) {}
}
void GameState::OnPlayerLeave(PlayerID a_Id) {
std::cout << "Game leave !" << std::endl;
}
void GameState::OnPlayerLeave(PlayerID a_Id) {}
} // namespace server
} // namespace td

View File

@@ -0,0 +1,24 @@
#include <server/state/GameState.h>
#include <server/state/GameStateHandler.h>
namespace td {
namespace server {
GameStateHandler::GameStateHandler(GameState& a_GameState, PlayerID a_PlayerId) : m_GameState(a_GameState), m_PlayerId(a_PlayerId) {}
// TODO: redo this
void GameStateHandler::Handle(const protocol::packets::SpawnTroopPacket& a_Packet) {
static const EntityCoords DEFAULT_POS(td::FpFloat(77), td::FpFloat(13));
td::protocol::commands::SpawnTroopCommand spawn(*a_Packet->m_Type, *a_Packet->m_Level, DEFAULT_POS, m_PlayerId);
m_GameState.m_Simulation.Handle(spawn);
}
// TODO: and this
void GameStateHandler::Handle(const protocol::packets::PlaceTowerPacket& a_Packet) {
td::protocol::commands::PlaceTowerCommand place(a_Packet->m_Type, m_PlayerId, a_Packet->m_Position);
m_GameState.m_Simulation.Handle(place);
}
} // namespace server
} // namespace td

View File

@@ -11,7 +11,7 @@ void LobbyState::HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packe
}
void LobbyState::Update(float a_Delta) {
SetNewState(std::make_shared<GameState>());
SetNewState(std::make_shared<GameState>(m_World));
}
void LobbyState::OnPlayerJoin(PlayerID a_Id) {
@@ -19,7 +19,7 @@ void LobbyState::OnPlayerJoin(PlayerID a_Id) {
}
void LobbyState::OnPlayerLeave(PlayerID a_Id) {
std::cout << "Lobby leave !" << std::endl;
}
} // namespace server

View File

@@ -2,6 +2,8 @@
#include <chrono>
#include <iostream>
namespace td {
namespace sim {
@@ -30,13 +32,13 @@ ClientSimulation::ClientSimulation(game::World& a_World, GameHistory&& a_History
ClientSimulation::ClientSimulation(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_History(std::numeric_limits<StepTime>::max()),
m_CurrentTime(0),
m_LastTime(GetTime()),
m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) {
Step();
// Step();
}
float ClientSimulation::Update() {
@@ -45,7 +47,7 @@ float ClientSimulation::Update() {
m_LastTime = GetTime();
if (m_CurrentTime > m_StepTime) {
Step();
m_CurrentTime -= m_StepTime;
m_CurrentTime %= m_StepTime;
}
return (float)m_CurrentTime / (float)m_StepTime;
}
@@ -69,25 +71,25 @@ void ClientSimulation::Handle(const protocol::packets::LockStepsPacket& a_LockSt
FastReplay();
}
void ClientSimulation::Handle(const protocol::packets::PredictCommandPacket& a_Predict) {
}
void ClientSimulation::Handle(const protocol::packets::PredictCommandPacket& a_Predict) {}
void ClientSimulation::FastForward(std::size_t a_Count) {
for (std::size_t i = 0; i < a_Count; i++) {
Step();
}
std::cout << "Was behind " << a_Count << " ticks !\n";
}
void ClientSimulation::FastReplay() {
if (m_LastValidStep >= m_CurrentStep)
if (m_LastValidStep + 1 >= m_CurrentStep)
return;
m_World.ResetSnapshots(m_LastSnapshot, m_LastSnapshot);
// TODO: cover holes
const std::size_t stepCount = m_CurrentStep - m_LastValidStep;
m_CurrentStep = m_LastValidStep;
FastForward(stepCount);
}

View File

@@ -1,36 +0,0 @@
#include <td/simulation/GameHistory.h>
namespace td {
namespace game {
GameHistory::GameHistory() : m_History(std::numeric_limits<HistorySizeType>::max()) {}
void GameHistory::SetLockStep(HistorySizeType a_Index, protocol::LockStep&& a_LockStep) {
m_History[a_Index] = std::move(a_LockStep);
}
const protocol::LockStep& GameHistory::GetLockStep(HistorySizeType a_Index) const {
return *m_History[a_Index];
}
bool GameHistory::HasLockStep(HistorySizeType a_Index) const {
return m_History[a_Index].has_value();
}
void GameHistory::FromPacket(protocol::pdata::LockSteps&& a_Steps) {
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
protocol::LockStep& step = a_Steps.m_LockSteps[i];
SetLockStep(i + a_Steps.m_FirstFrameNumber, std::move(step));
}
}
protocol::packets::LockStepsPacket GameHistory::ToPacket(HistorySizeType a_StartIndex) {
std::array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps;
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
steps[i] = GetLockStep(a_StartIndex + i);
}
return {a_StartIndex, std::move(steps)};
}
} // namespace game
} // namespace td

View File

@@ -4,20 +4,24 @@ namespace td {
namespace sim {
ServerSimulation::ServerSimulation(game::World& a_World, std::uint64_t a_StepTime) :
m_World(a_World), m_StepTime(a_StepTime), m_CurrentTime(0) {}
m_World(a_World), m_StepTime(a_StepTime), m_CurrentTime(0), m_History(std::numeric_limits<StepTime>::max()) {}
protocol::packets::LockStepsPacket ServerSimulation::Update() {
std::lock_guard<std::mutex> lock(m_Mutex);
m_World.Tick(m_History[m_CurrentTime], FpFloat(m_StepTime) / FpFloat(1000));
m_CurrentTime++;
return MakePacket();
}
protocol::packets::LockStepsPacket ServerSimulation::MakePacket() {
std::array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> nextSteps;
std::copy(m_History.begin() + m_CurrentTime, m_History.begin() + m_CurrentTime + nextSteps.size(), nextSteps.begin());
return {m_CurrentTime, std::move(nextSteps)};
}
template<typename T>
template <typename T>
void AddToCommandHistory(protocol::LockStep& a_LockStep, const T& a_Cmd) {
a_LockStep.push_back({std::make_shared<T>(a_Cmd)});
}