[Change] Switched to an input-based reconcilation
Replaced the previous correction models with a full "Reconciliation by Replaying Inputs system".
This commit is contained in:
parent
8d59e79f8a
commit
f653c34baf
@ -4,7 +4,7 @@ namespace BettolaLib {
|
|||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
enum class MessageType : unsigned char {
|
enum class MessageType : unsigned char {
|
||||||
PlayerPosition,
|
PlayerInput,
|
||||||
PlayerState,
|
PlayerState,
|
||||||
GameState,
|
GameState,
|
||||||
PlayerId,
|
PlayerId,
|
||||||
|
|||||||
@ -5,10 +5,14 @@
|
|||||||
namespace BettolaLib {
|
namespace BettolaLib {
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
struct PlayerPosMessage {
|
struct PlayerInputMessage {
|
||||||
unsigned int player_id;
|
unsigned int player_id;
|
||||||
float x;
|
unsigned int sequence_number;
|
||||||
float y;
|
bool up;
|
||||||
|
bool down;
|
||||||
|
bool left;
|
||||||
|
bool right;
|
||||||
|
float dt;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace Network. */
|
} /* namespace Network. */
|
||||||
@ -9,6 +9,7 @@ struct PlayerStateMessage {
|
|||||||
unsigned int player_id;
|
unsigned int player_id;
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
|
unsigned int last_processed_sequence;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace Network. */
|
} /* namespace Network. */
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
#include "math/mat4.h"
|
#include "math/mat4.h"
|
||||||
#include "network/message.h"
|
#include "network/message.h"
|
||||||
#include "network/game_state_message.h"
|
#include "network/game_state_message.h"
|
||||||
#include "network/player_pos_message.h"
|
#include "network/player_input_message.h"
|
||||||
|
|
||||||
/* Dacav's resolution ;) */
|
/* Dacav's resolution ;) */
|
||||||
const int SCREEN_WIDTH = 800;
|
const int SCREEN_WIDTH = 800;
|
||||||
@ -33,9 +33,7 @@ Bettola::Bettola(void) :
|
|||||||
_vao(0),
|
_vao(0),
|
||||||
_vbo(0),
|
_vbo(0),
|
||||||
_our_player_id(0),
|
_our_player_id(0),
|
||||||
_needs_correction(false),
|
_input_sequence_number(0) {
|
||||||
_correction_target_x(0.0f),
|
|
||||||
_correction_target_y(0.0f) {
|
|
||||||
|
|
||||||
memset(&_server_addr, 0, sizeof(_server_addr));
|
memset(&_server_addr, 0, sizeof(_server_addr));
|
||||||
}
|
}
|
||||||
@ -203,41 +201,33 @@ void Bettola::update(double dt) {
|
|||||||
|
|
||||||
_player.update(dt);
|
_player.update(dt);
|
||||||
|
|
||||||
if(_needs_correction) {
|
|
||||||
const float interp_speed = 20.0f; /* We'll use a slightly higher speed for self-correction. */
|
|
||||||
float current_x = _player.get_x();
|
|
||||||
float current_y = _player.get_y();
|
|
||||||
_player.set_position(current_x + (_correction_target_x - current_x) * interp_speed * dt,
|
|
||||||
current_y + (_correction_target_y - current_y) * interp_speed * dt);
|
|
||||||
|
|
||||||
/* If we are very close.. Stop correcting. */
|
|
||||||
if(std::abs(current_x - _correction_target_x) < 0.1f &&
|
|
||||||
std::abs(current_y - _correction_target_y) < 0.1f) {
|
|
||||||
_needs_correction = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& remote_player : _remote_players) {
|
for(auto& remote_player : _remote_players) {
|
||||||
remote_player.update(dt);
|
remote_player.update(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_our_player_id > 0) {
|
if(_our_player_id > 0) {
|
||||||
/* Send player position to server via UDP.. */
|
/* Sent our current input state to the server. */
|
||||||
|
BettolaLib::Network::PlayerInputMessage input_msg;
|
||||||
|
input_msg.player_id = _our_player_id;
|
||||||
|
input_msg.sequence_number = ++_input_sequence_number;
|
||||||
|
input_msg.up = _input.up;
|
||||||
|
input_msg.down = _input.down;
|
||||||
|
input_msg.left = _input.left;
|
||||||
|
input_msg.right = _input.right;
|
||||||
|
input_msg.dt = dt;
|
||||||
|
|
||||||
BettolaLib::Network::MessageHeader header;
|
BettolaLib::Network::MessageHeader header;
|
||||||
header.type = BettolaLib::Network::MessageType::PlayerPosition;
|
header.type = BettolaLib::Network::MessageType::PlayerInput;
|
||||||
header.size = sizeof(BettolaLib::Network::PlayerPosMessage);
|
header.size = sizeof(input_msg);
|
||||||
|
|
||||||
BettolaLib::Network::PlayerPosMessage msg;
|
char buffer[sizeof(header) + sizeof(input_msg)];
|
||||||
msg.player_id = _our_player_id;
|
|
||||||
msg.x = _our_player_id;
|
|
||||||
msg.x = _player.get_x();
|
|
||||||
msg.y = _player.get_y();
|
|
||||||
|
|
||||||
char buffer[sizeof(header) + sizeof(msg)];
|
|
||||||
memcpy(buffer, &header, sizeof(header));
|
memcpy(buffer, &header, sizeof(header));
|
||||||
memcpy(buffer + sizeof(header), &msg, sizeof(msg));
|
memcpy(buffer+sizeof(header), &input_msg, sizeof(input_msg));
|
||||||
|
|
||||||
_udp_socket.send_to(buffer, sizeof(buffer), _server_addr);
|
_udp_socket.send_to(buffer, sizeof(buffer), _server_addr);
|
||||||
|
|
||||||
|
/* Store for reconciliation. */
|
||||||
|
_pending_inputs.push_back(input_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char window_title[256];
|
static char window_title[256];
|
||||||
@ -303,22 +293,24 @@ void Bettola::process_game_state(const BettolaLib::Network::GameStateMessage& ms
|
|||||||
const auto& player_state = msg.players[i];
|
const auto& player_state = msg.players[i];
|
||||||
|
|
||||||
if(player_state.player_id == _our_player_id) {
|
if(player_state.player_id == _our_player_id) {
|
||||||
/*
|
/* This is our player. Reconcile. */
|
||||||
* This is our player. Reconcile predicted state with the
|
_player.set_position(player_state.x, player_state.y);
|
||||||
* server's authoritative state.
|
|
||||||
*/
|
|
||||||
printf(" -> Processing remote player %u at (%.2f, %.2f)\n", player_state.player_id,
|
|
||||||
player_state.x, player_state.y);
|
|
||||||
/* This is us! Reconcile our predicted state. */
|
|
||||||
float dx = _player.get_x() - player_state.x;
|
|
||||||
float dy = _player.get_y() - player_state.y;
|
|
||||||
if((dx*dx+dy*dy) > 0.0001f) { /* If distance is not negligibale. */
|
|
||||||
/* Simple correction, snap to server position. */
|
|
||||||
//_player.set_position(player_state.x, player_state.y);
|
|
||||||
|
|
||||||
_needs_correction = true;
|
/* Remove all inputs from our history that the server has processed. */
|
||||||
_correction_target_x = player_state.x;
|
while(!_pending_inputs.empty() &&
|
||||||
_correction_target_y = player_state.y;
|
_pending_inputs.front().sequence_number <= player_state.last_processed_sequence) {
|
||||||
|
|
||||||
|
_pending_inputs.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now, re-apply any remaining inputs that the server hasn't
|
||||||
|
* seen yet. This'll bring the client up to date with most recent inputs.
|
||||||
|
*/
|
||||||
|
for(const auto& input : _pending_inputs) {
|
||||||
|
float dir_x = (input.right ? 1.0f : 0.0f) - (input.left ? 1.0f : 0.0f);
|
||||||
|
float dir_y = (input.down ? 1.0f : 0.0f) - (input.up ? 1.0f : 0.0f);
|
||||||
|
_player.set_velocity_direction(dir_x, dir_y);
|
||||||
|
_player.update(input.dt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Remote player. Find if we already know about them.. */
|
/* Remote player. Find if we already know about them.. */
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
#include <deque>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -9,6 +10,7 @@
|
|||||||
#include "game/remote_player.h"
|
#include "game/remote_player.h"
|
||||||
#include "math/mat4.h"
|
#include "math/mat4.h"
|
||||||
#include "network/tcpsocket.h"
|
#include "network/tcpsocket.h"
|
||||||
|
#include "network/player_input_message.h"
|
||||||
#include "network/udpsocket.h"
|
#include "network/udpsocket.h"
|
||||||
namespace BettolaLib { namespace Network { struct GameStateMessage; } }
|
namespace BettolaLib { namespace Network { struct GameStateMessage; } }
|
||||||
|
|
||||||
@ -61,7 +63,6 @@ private:
|
|||||||
BettolaLib::Network::UDPSocket _udp_socket;
|
BettolaLib::Network::UDPSocket _udp_socket;
|
||||||
sockaddr_in _server_addr;
|
sockaddr_in _server_addr;
|
||||||
|
|
||||||
bool _needs_correction;
|
unsigned int _input_sequence_number;
|
||||||
float _correction_target_x;
|
std::deque<BettolaLib::Network::PlayerInputMessage> _pending_inputs;
|
||||||
float _correction_target_y;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "network/game_state_message.h"
|
#include "network/game_state_message.h"
|
||||||
#include "network/player_pos_message.h"
|
#include "network/player_input_message.h"
|
||||||
#include "network/tcpsocket.h"
|
#include "network/tcpsocket.h"
|
||||||
#include "network/message.h"
|
#include "network/message.h"
|
||||||
#include "network/udpsocket.h"
|
#include "network/udpsocket.h"
|
||||||
@ -27,12 +27,18 @@ void Game::remove_player(unsigned int player_id) {
|
|||||||
_players.end());
|
_players.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
void Game::process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr) {
|
||||||
const char* buffer, const sockaddr_in& from_addr) {
|
if(size < sizeof(BettolaLib::Network::MessageHeader)) return;
|
||||||
|
|
||||||
if(header.type == BettolaLib::Network::MessageType::PlayerPosition) {
|
BettolaLib::Network::MessageHeader header;
|
||||||
BettolaLib::Network::PlayerPosMessage msg;
|
memcpy(&header, buffer, sizeof(header));
|
||||||
memcpy(&msg, buffer, sizeof(msg));
|
|
||||||
|
if(header.type == BettolaLib::Network::MessageType::PlayerInput) {
|
||||||
|
if(size < sizeof(BettolaLib::Network::MessageHeader)
|
||||||
|
+ sizeof(BettolaLib::Network::PlayerInputMessage)) return;
|
||||||
|
|
||||||
|
BettolaLib::Network::PlayerInputMessage msg;
|
||||||
|
memcpy(&msg, buffer + sizeof(header), sizeof(msg));
|
||||||
|
|
||||||
Player* player = get_player_by_id(msg.player_id);
|
Player* player = get_player_by_id(msg.player_id);
|
||||||
if(player) {
|
if(player) {
|
||||||
@ -40,7 +46,11 @@ void Game::handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
|||||||
printf("Bettola Server: Associated UDP address for player %u\n", msg.player_id);
|
printf("Bettola Server: Associated UDP address for player %u\n", msg.player_id);
|
||||||
player->set_udp_addr(from_addr);
|
player->set_udp_addr(from_addr);
|
||||||
}
|
}
|
||||||
player->set_position(msg.x, msg.y);
|
player->set_last_processed_sequence(msg.sequence_number);
|
||||||
|
float dir_x = (msg.right ? 1.0f : 0.0f) - (msg.left ? 1.0f : 0.0f);
|
||||||
|
float dir_y = (msg.down ? 1.0f : 0.0f) - (msg.up ? 1.0f : 0.0f);
|
||||||
|
player->set_velocity_direction(dir_x, dir_y);
|
||||||
|
player->update(msg.dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,6 +64,7 @@ void Game::broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket) {
|
|||||||
msg.players[i].player_id = _players[i]->get_id();
|
msg.players[i].player_id = _players[i]->get_id();
|
||||||
msg.players[i].x = _players[i]->get_x();
|
msg.players[i].x = _players[i]->get_x();
|
||||||
msg.players[i].y = _players[i]->get_y();
|
msg.players[i].y = _players[i]->get_y();
|
||||||
|
msg.players[i].last_processed_sequence = _players[i]->get_last_processed_sequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
BettolaLib::Network::MessageHeader header;
|
BettolaLib::Network::MessageHeader header;
|
||||||
|
|||||||
@ -12,8 +12,7 @@ public:
|
|||||||
Player* add_player(BettolaLib::Network::TCPSocket* socket);
|
Player* add_player(BettolaLib::Network::TCPSocket* socket);
|
||||||
void remove_player(unsigned int player_id);
|
void remove_player(unsigned int player_id);
|
||||||
|
|
||||||
void handle_udp_message(const BettolaLib::Network::MessageHeader& header,
|
void process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr);
|
||||||
const char* buffer, const sockaddr_in& from_addr);
|
|
||||||
void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket);
|
void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket);
|
||||||
|
|
||||||
Player* get_player_by_socket(const BettolaLib::Network::TCPSocket* socket);
|
Player* get_player_by_socket(const BettolaLib::Network::TCPSocket* socket);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "network/tcpsocket.h"
|
#include "network/tcpsocket.h"
|
||||||
@ -15,6 +16,24 @@ Player::Player(BettolaLib::Network::TCPSocket* socket) :
|
|||||||
}
|
}
|
||||||
_id = _next_player_id++;
|
_id = _next_player_id++;
|
||||||
_x = 0.0f; _y = 0.0f;
|
_x = 0.0f; _y = 0.0f;
|
||||||
|
_vx = 0.0f; _vy = 0.0f;
|
||||||
|
_speed = 200.0f; /* Must match client! */
|
||||||
|
_last_processed_sequence = 0;
|
||||||
memset(&_udp_addr, 0, sizeof(_udp_addr));
|
memset(&_udp_addr, 0, sizeof(_udp_addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::update(double dt) {
|
||||||
|
_x += _vx * dt;
|
||||||
|
_y += _vy * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::set_velocity_direction(float dir_x, float dir_y) {
|
||||||
|
if(dir_x == 0.0f && dir_y == 0.0f) {
|
||||||
|
_vx = _vy = 0.0f;
|
||||||
|
} else {
|
||||||
|
float mag = sqrt(dir_x * dir_x + dir_y * dir_y);
|
||||||
|
_vx = (dir_x / mag) * _speed;
|
||||||
|
_vy = (dir_y / mag) * _speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ class Player {
|
|||||||
public:
|
public:
|
||||||
Player(BettolaLib::Network::TCPSocket* socket);
|
Player(BettolaLib::Network::TCPSocket* socket);
|
||||||
|
|
||||||
|
void update(double dt);
|
||||||
|
void set_velocity_direction(float dir_x, float dir_y);
|
||||||
void set_position(float x, float y) { _x = x; _y = y; }
|
void set_position(float x, float y) { _x = x; _y = y; }
|
||||||
void set_udp_addr(const sockaddr_in& addr) { _udp_addr = addr; _has_udp_addr = true; }
|
void set_udp_addr(const sockaddr_in& addr) { _udp_addr = addr; _has_udp_addr = true; }
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ public:
|
|||||||
BettolaLib::Network::TCPSocket& get_socket(void) const { return *_socket; }
|
BettolaLib::Network::TCPSocket& get_socket(void) const { return *_socket; }
|
||||||
const sockaddr_in& get_udp_addr(void) const { return _udp_addr; }
|
const sockaddr_in& get_udp_addr(void) const { return _udp_addr; }
|
||||||
bool has_udp_addr(void) const { return _has_udp_addr; }
|
bool has_udp_addr(void) const { return _has_udp_addr; }
|
||||||
|
void set_last_processed_sequence(unsigned int sequence) { _last_processed_sequence = sequence; }
|
||||||
|
unsigned int get_last_processed_sequence(void) const { return _last_processed_sequence; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static unsigned int _next_player_id;
|
static unsigned int _next_player_id;
|
||||||
@ -28,4 +32,7 @@ private:
|
|||||||
BettolaLib::Network::TCPSocket* _socket;
|
BettolaLib::Network::TCPSocket* _socket;
|
||||||
sockaddr_in _udp_addr;
|
sockaddr_in _udp_addr;
|
||||||
bool _has_udp_addr;
|
bool _has_udp_addr;
|
||||||
|
unsigned int _last_processed_sequence;
|
||||||
|
float _vx, _vy;
|
||||||
|
float _speed;
|
||||||
};
|
};
|
||||||
|
|||||||
10
srv/main.cpp
10
srv/main.cpp
@ -12,7 +12,7 @@
|
|||||||
#include "bettola/network/udpsocket.h"
|
#include "bettola/network/udpsocket.h"
|
||||||
#include "bettola/network/net_common.h"
|
#include "bettola/network/net_common.h"
|
||||||
#include "bettola/network/message.h"
|
#include "bettola/network/message.h"
|
||||||
#include "bettola/network/player_pos_message.h"
|
#include "bettola/network/player_input_message.h"
|
||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
@ -126,13 +126,7 @@ int main(void) {
|
|||||||
ssize_t bytes_received;
|
ssize_t bytes_received;
|
||||||
|
|
||||||
while((bytes_received = udp_socket.recv_from(buffer, sizeof(buffer), from_addr)) > 0) {
|
while((bytes_received = udp_socket.recv_from(buffer, sizeof(buffer), from_addr)) > 0) {
|
||||||
if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader)) {
|
game.process_udp_message(buffer, bytes_received, from_addr);
|
||||||
BettolaLib::Network::MessageHeader header;
|
|
||||||
memcpy(&header, buffer, sizeof(header));
|
|
||||||
if(bytes_received >= sizeof(BettolaLib::Network::MessageHeader) + header.size) {
|
|
||||||
game.handle_udp_message(header, buffer+sizeof(BettolaLib::Network::MessageHeader), from_addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user