From ac41db13c9d64f5ef12b26c335d57504d02fd2fd Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Wed, 16 Sep 2015 11:46:58 +0200 Subject: [PATCH] second attempt at "nice" client state correction --- src/app/ServerState.cpp | 11 +++++------ src/app/ServerState.hpp | 1 - src/client/InteractiveState.hpp | 8 ++++---- src/client/client.cpp | 30 ++++++++++++++++++++---------- src/net/ClientConnection.hpp | 2 ++ src/net/net.cpp | 30 ++++++++++++++++++++---------- 6 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/app/ServerState.cpp b/src/app/ServerState.cpp index d980cbc..13abdc9 100644 --- a/src/app/ServerState.cpp +++ b/src/app/ServerState.cpp @@ -24,14 +24,12 @@ ServerState::ServerState( , skeletons() , spawner(world, skeletons, gc.seed) , server(sc, world) -, loop_timer(16) -, push_timer(16) { +, loop_timer(16) { TextureIndex tex_index; env.loader.LoadBlockTypes("default", block_types, tex_index); skeletons.LoadHeadless(); loop_timer.Start(); - push_timer.Start(); std::cout << "listening on UDP port " << sc.port << std::endl; } @@ -46,16 +44,17 @@ void ServerState::Handle(const SDL_Event &event) { void ServerState::Update(int dt) { loop_timer.Update(dt); - push_timer.Update(dt); server.Handle(); + int world_dt = 0; while (loop_timer.HitOnce()) { spawner.Update(loop_timer.Interval()); world.Update(loop_timer.Interval()); + world_dt += loop_timer.Interval(); loop_timer.PopIteration(); } chunk_loader.Update(dt); - if (push_timer.Hit()) { - server.Update(dt); + if (world_dt > 0) { + server.Update(world_dt); } } diff --git a/src/app/ServerState.hpp b/src/app/ServerState.hpp index afa7840..db21d0f 100644 --- a/src/app/ServerState.hpp +++ b/src/app/ServerState.hpp @@ -43,7 +43,6 @@ private: Spawner spawner; Server server; IntervalTimer loop_timer; - IntervalTimer push_timer; }; diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index eaca4c9..09a9919 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -38,7 +38,7 @@ public: void Update(int dt) override; void Render(Viewport &) override; - void PushPlayerUpdate(const Entity &); + void PushPlayerUpdate(const Entity &, int dt); void MergePlayerCorrection(std::uint16_t, const EntityState &); private: @@ -50,13 +50,13 @@ private: ChunkRenderer chunk_renderer; Skeletons skeletons; IntervalTimer loop_timer; - IntervalTimer update_timer; struct PlayerHistory { EntityState state; + int delta_t; std::uint16_t packet; - PlayerHistory(EntityState s, std::uint16_t p) - : state(s), packet(p) { } + PlayerHistory(EntityState s, int dt, std::uint16_t p) + : state(s), delta_t(dt), packet(p) { } }; std::list player_hist; diff --git a/src/client/client.cpp b/src/client/client.cpp index dbdaa73..72d860d 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -57,7 +57,6 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id) , chunk_renderer(*interface.GetPlayer().chunks) , skeletons() , loop_timer(16) -, update_timer(16) , player_hist() { TextureIndex tex_index; master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index); @@ -67,7 +66,6 @@ InteractiveState::InteractiveState(MasterState &master, uint32_t player_id) // TODO: better solution for initializing HUD interface.SelectNext(); loop_timer.Start(); - update_timer.Start(); } void InteractiveState::OnEnter() { @@ -104,20 +102,21 @@ void InteractiveState::Handle(const SDL_Event &event) { void InteractiveState::Update(int dt) { loop_timer.Update(dt); - update_timer.Update(dt); master.Update(dt); interface.Update(dt); + int world_dt = 0; while (loop_timer.HitOnce()) { world.Update(loop_timer.Interval()); + world_dt += loop_timer.Interval(); loop_timer.PopIteration(); } chunk_renderer.Update(dt); Entity &player = *interface.GetPlayer().entity; - if (update_timer.Hit()) { - PushPlayerUpdate(player); + if (world_dt > 0) { + PushPlayerUpdate(player, world_dt); } glm::mat4 trans = player.Transform(player.ChunkCoords()); @@ -128,13 +127,14 @@ void InteractiveState::Update(int dt) { master.GetEnv().audio.Orientation(dir, up); } -void InteractiveState::PushPlayerUpdate(const Entity &player) { +void InteractiveState::PushPlayerUpdate(const Entity &player, int dt) { std::uint16_t packet = master.GetClient().SendPlayerUpdate(player); if (player_hist.size() < 16) { - player_hist.emplace_back(player.GetState(), packet); + player_hist.emplace_back(player.GetState(), dt, packet); } else { auto entry = player_hist.begin(); entry->state = player.GetState(); + entry->delta_t = dt; entry->packet = packet; player_hist.splice(player_hist.end(), player_hist, entry); } @@ -146,9 +146,16 @@ void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &co auto entry = player_hist.begin(); auto end = player_hist.end(); + // we may have received an older packet + int pack_diff = int16_t(seq) - int16_t(entry->packet); + if (pack_diff < 0) { + // indeed we have, just ignore it + return; + } + // drop anything older than the fix while (entry != end) { - int pack_diff = int16_t(seq) - int16_t(entry->packet); + pack_diff = int16_t(seq) - int16_t(entry->packet); if (pack_diff > 0) { entry = player_hist.erase(entry); } else { @@ -167,7 +174,7 @@ void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &co while (entry != end) { replay_state.velocity = entry->state.velocity; - replay_state.Update(16); + replay_state.Update(entry->delta_t); entry->state.chunk_pos = replay_state.chunk_pos; entry->state.block_pos = replay_state.block_pos; ++entry; @@ -180,7 +187,10 @@ void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &co return; } - constexpr float warp_thresh = 1.0f; + // if offset > 10cm, warp the player + // otherwise, move at most 1cm per frame towards + // the fixed position (160ms, so shouldn't be too noticeable) + constexpr float warp_thresh = 0.01f; // (1/10)^2 constexpr float max_disp = 0.0001f; // (1/100)^2 if (disp_squared > warp_thresh) { diff --git a/src/net/ClientConnection.hpp b/src/net/ClientConnection.hpp index 7fb797b..f344efd 100644 --- a/src/net/ClientConnection.hpp +++ b/src/net/ClientConnection.hpp @@ -4,6 +4,7 @@ #include "Connection.hpp" #include "ConnectionHandler.hpp" #include "../app/IntervalTimer.hpp" +#include "../world/EntityState.hpp" #include #include @@ -70,6 +71,7 @@ private: Entity *player; std::list spawns; unsigned int confirm_wait; + EntityState player_update_state; std::uint16_t player_update_pack; IntervalTimer player_update_timer; diff --git a/src/net/net.cpp b/src/net/net.cpp index 98954bf..6bd561c 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -128,6 +128,7 @@ ClientConnection::ClientConnection(Server &server, const IPaddress &addr) , player(nullptr) , spawns() , confirm_wait(0) +, player_update_state() , player_update_pack(0) , player_update_timer(1500) { conn.SetHandler(this); @@ -253,11 +254,20 @@ void ClientConnection::SendUpdate(SpawnStatus &status) { } void ClientConnection::CheckPlayerFix() { - // check always succeeds for now ;) - auto pack = Packet::Make(server.GetPacket()); - pack.WritePacketSeq(player_update_pack); - pack.WritePlayer(Player()); - conn.Send(server.GetPacket(), server.GetSocket()); + // player_update_state's position holds the client's most recent prediction + glm::vec3 diff = player_update_state.Diff(Player().GetState()); + float dist_squared = dot(diff, diff); + + // if client's prediction is off by more than 1cm, send + // our (authoritative) state back so it can fix it + constexpr float fix_thresh = 0.0001f; + + if (dist_squared > fix_thresh) { + auto pack = Packet::Make(server.GetPacket()); + pack.WritePacketSeq(player_update_pack); + pack.WritePlayer(Player()); + conn.Send(server.GetPacket(), server.GetSocket()); + } } void ClientConnection::AttachPlayer(Entity &new_player) { @@ -324,6 +334,7 @@ void ClientConnection::On(const Packet::Login &pack) { response.WriteWorldName(server.GetWorld().Name()); conn.Send(server.GetPacket(), server.GetSocket()); // set up update tracking + player_update_state = new_player->GetState(); player_update_pack = pack.Seq(); player_update_timer.Reset(); player_update_timer.Start(); @@ -347,11 +358,10 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) { player_update_timer.Reset(); if (pack_diff > 0 || overdue) { player_update_pack = pack.Seq(); - // TODO: do client input validation here - EntityState new_state; - pack.ReadPlayerState(new_state); - Player().Velocity(new_state.velocity); - Player().Orientation(new_state.orient); + pack.ReadPlayerState(player_update_state); + // accept velocity and orientation as "user input" + Player().Velocity(player_update_state.velocity); + Player().Orientation(player_update_state.orient); } } -- 2.39.2