pandemonium_engine/modules/network_synchronizer/networked_controller.h

528 lines
19 KiB
C++
Raw Normal View History

#ifndef NETWORKED_CONTROLLER_H
#define NETWORKED_CONTROLLER_H
/*************************************************************************/
/* networked_controller.h */
/*************************************************************************/
2023-12-18 00:18:53 +01:00
/* This file is part of: */
/* PANDEMONIUM ENGINE */
/* https://github.com/Relintai/pandemonium_engine */
/*************************************************************************/
2023-12-18 00:18:53 +01:00
/* Copyright (c) 2022-present Péter Magyar. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
/**
@author AndreaCatania
*/
#include "scene/main/node.h"
#include "data_buffer.h"
#include "interpolator.h"
#include "net_utilities.h"
#include <deque>
class SceneSynchronizer;
struct Controller;
struct ServerController;
struct PlayerController;
struct DollController;
struct NoNetController;
/// The `NetworkedController` is responsible to sync the `Player` inputs between
/// the peers. This allows to control a character, or an object with high precision
/// and replicates that movement on all connected peers.
///
/// The `NetworkedController` will sync inputs, based on those will perform
/// operations.
2022-12-22 19:51:25 +01:00
/// The result of these operations, are guaranteed to be the same across the
/// peers, if we stay under the assumption that the initial state is the same.
///
/// Is possible to use the `SceneSynchronizer` to keep the state in sync with the
/// peers.
///
// # Implementation details
//
// The `NetworkedController` perform different operations depending where it's
// instantiated.
// The most important part is inside the `PlayerController`, `ServerController`,
// `DollController`, `NoNetController`.
class NetworkedController : public Node {
GDCLASS(NetworkedController, Node);
friend class SceneSynchronizer;
public:
enum ControllerType {
CONTROLLER_TYPE_NULL,
CONTROLLER_TYPE_NONETWORK,
CONTROLLER_TYPE_PLAYER,
CONTROLLER_TYPE_SERVER,
CONTROLLER_TYPE_DOLL
};
private:
/// The input storage size is used to cap the amount of inputs collected by
/// the `PlayerController`.
///
/// The server sends a message, to all the connected peers, notifing its
/// status at a fixed interval.
/// The peers, after receiving this update, removes all the old inputs until
/// that moment.
///
/// `input_storage_size`: is too small, the player may stop collect
/// - Too small value makes the `PlayerController` stop collecting inputs
/// too early, in case of lag.
/// - Too big values may introduce too much latency, because the player keep
/// pushing new inputs without receiving the server snapshot.
///
/// With 60 iteration per seconds a good value is `180` (60 * 3) so the
/// `PlayerController` can be at max 3 seconds ahead the `ServerController`.
int player_input_storage_size = 180;
/// Amount of time an inputs is re-sent to each peer.
/// Resenging inputs is necessary because the packets may be lost since as
/// they are sent in an unreliable way.
int max_redundant_inputs = 5;
/// Time in seconds between each `tick_speedup` that the server sends to the
/// client.
real_t tick_speedup_notification_delay = 0.33;
/// The connection quality is established by watching the time passed
/// between each input is received.
/// The more this time is the same the more the connection health is good.
///
/// The `network_traced_frames` defines how many frames have
/// to be used to establish the connection quality.
/// - Big values make the mechanism too slow.
/// - Small values make the mechanism too sensible.
int network_traced_frames = 120;
/// Sensitivity to network oscillations. The value is in seconds and can be
/// used to establish the connection quality.
///
/// For each input, the time needed for its arrival is traced; the standard
/// deviation of these is divided by `net_sensitivity`: the result is
/// the connection quality.
///
/// The more the time needed for each batch to arrive is different the
/// bigger this value is: when this value approaches to
/// `net_sensitivity` (or even surpasses it) the bad the connection is.
///
/// The result is the `connection_poorness` that goes from 0 to 1 and is
/// used to decide the `optimal_frame_delay` that is interpolated between
/// `min_frames_delay` and `max_frames_delay`.
real_t net_sensitivity = 0.1;
/// The `ServerController` will try to keep a margin of error, so that
/// network oscillations doesn't leave the `ServerController` without
/// inputs.
///
/// This margin of error is called `optimal_frame_delay` and it changes
/// depending on the connection health:
/// it can go from `min_frames_delay` to `max_frames_delay`.
int min_frames_delay = 2;
int max_frames_delay = 6;
/// Rate at which the tick speed changes, so the `optimal_frame_delay` is
/// matched.
real_t tick_acceleration = 2.0;
2022-12-22 19:51:25 +01:00
/// Collect rate (in frames) used by the server to establish when to collect
/// the state for a particular peer.
/// It's possible to scale down this rate, for a particular peer,
/// using the function: set_doll_collect_rate_factor(peer, factor);
/// Current default is 10Hz.
///
2022-12-22 19:51:25 +01:00
/// The collected state is not immediately sent to the clients, rather it's
/// delayed so to be sent in batch. The states marked as important are
/// always collected.
int doll_epoch_collect_rate = 1;
/// The time rate at which a new batch is sent.
real_t doll_epoch_batch_sync_rate = 0.25;
/// The doll interpolator will try to keep a margin of error, so that network
/// oscillations doesn't make the dolls freeze.
///
/// This margin of error is called `optimal_frame_delay` and it changes
/// depending on the connection health:
/// it can go from `doll_min_frames_delay` to `doll_max_frames_delay`.
int doll_min_frames_delay = 2;
int doll_max_frames_delay = 5;
/// Max speedup / slowdown the doll can apply to recover its epoch buffer size.
real_t doll_interpolation_max_speedup = 0.2;
/// The connection quality is established by watching the time passed
/// between each batch arrival.
/// The more this time is the same the more the connection health is good.
///
/// The `doll_connection_stats_frame_span` defines how many frames have
/// to be used to establish the connection quality.
/// - Big values make the mechanism too slow.
/// - Small values make the mechanism too sensible.
/// The correct value should be give considering the
/// `doll_epoch_batch_sync_rate`.
int doll_connection_stats_frame_span = 30;
/// Sensitivity to network oscillations. The value is in seconds and can be
/// used to establish the connection quality.
///
/// For each batch, the time needed for its arrival is traced; the standard
/// deviation of these is divided by `doll_net_sensitivity`: the result is
/// the connection quality.
///
/// The more the time needed for each batch to arrive is different the
/// bigger this value is: when this value approaches to
/// `doll_net_sensitivity` (or even surpasses it) the bad the connection is.
///
/// The result is the `connection_poorness` that goes from 0 to 1 and is
/// used to decide the `optimal_frames_delay` that is interpolated between
/// `doll_min_frames_delay` and `doll_max_frames_delay`.
real_t doll_net_sensitivity = 0.2;
/// Just after a bad connection phase, may happen that all the sent epochs
2022-12-22 19:51:25 +01:00
/// are received at once. To make sure the actor is always the more up-to-date
/// possible, the number of epochs the actor has to fetch is clamped.
/// When `doll_max_delay` is surpassed the doll is teleported forward the
/// timeline so to be the more update possible.
uint64_t doll_max_delay = 60;
ControllerType controller_type = CONTROLLER_TYPE_NULL;
Controller *controller = nullptr;
DataBuffer inputs_buffer;
SceneSynchronizer *scene_synchronizer = nullptr;
bool packet_missing = false;
bool has_player_new_input = false;
public:
static void _bind_methods();
public:
NetworkedController();
void set_player_input_storage_size(int p_size);
int get_player_input_storage_size() const;
void set_max_redundant_inputs(int p_max);
int get_max_redundant_inputs() const;
void set_tick_speedup_notification_delay(real_t p_delay);
real_t get_tick_speedup_notification_delay() const;
void set_network_traced_frames(int p_size);
int get_network_traced_frames() const;
void set_min_frames_delay(int p_val);
int get_min_frames_delay() const;
void set_max_frames_delay(int p_val);
int get_max_frames_delay() const;
void set_net_sensitivity(real_t p_val);
real_t get_net_sensitivity() const;
void set_tick_acceleration(real_t p_acceleration);
real_t get_tick_acceleration() const;
void set_doll_epoch_collect_rate(int p_rate);
int get_doll_epoch_collect_rate() const;
void set_doll_epoch_batch_sync_rate(real_t p_rate);
real_t get_doll_epoch_batch_sync_rate() const;
void set_doll_min_frames_delay(int p_min);
int get_doll_min_frames_delay() const;
void set_doll_max_frames_delay(int p_max);
int get_doll_max_frames_delay() const;
void set_doll_interpolation_max_speedup(real_t p_speedup);
real_t get_doll_interpolation_max_speedup() const;
void set_doll_connection_stats_frame_span(int p_span);
int get_doll_connection_stats_frame_span() const;
void set_doll_net_sensitivity(real_t p_sensitivity);
real_t get_doll_net_sensitivity() const;
void set_doll_max_delay(uint32_t p_max_delay);
uint32_t get_doll_max_delay() const;
uint32_t get_current_input_id() const;
const DataBuffer &get_inputs_buffer() const {
return inputs_buffer;
}
DataBuffer &get_inputs_buffer_mut() {
return inputs_buffer;
}
/// Returns the pretended delta used by the player.
real_t player_get_pretended_delta(uint32_t p_physics_ticks_per_seconds) const;
void mark_epoch_as_important();
void set_doll_collect_rate_factor(int p_peer, real_t p_factor);
void set_doll_peer_active(int p_peer_id, bool p_active);
void pause_notify_dolls();
bool process_instant(int p_i, real_t p_delta);
/// Returns the server controller or nullptr if this is not a server.
ServerController *get_server_controller();
const ServerController *get_server_controller() const;
/// Returns the player controller or nullptr if this is not a player.
PlayerController *get_player_controller();
const PlayerController *get_player_controller() const;
/// Returns the doll controller or nullptr if this is not a doll.
DollController *get_doll_controller();
const DollController *get_doll_controller() const;
/// Returns the no net controller or nullptr if this is not a no net.
NoNetController *get_nonet_controller();
const NoNetController *get_nonet_controller() const;
bool is_server_controller() const;
bool is_player_controller() const;
bool is_doll_controller() const;
bool is_nonet_controller() const;
public:
void set_inputs_buffer(const BitArray &p_new_buffer, uint32_t p_metadata_size_in_bit, uint32_t p_size_in_bit);
void set_scene_synchronizer(SceneSynchronizer *p_synchronizer);
SceneSynchronizer *get_scene_synchronizer() const;
bool has_scene_synchronizer() const;
/* On server rpc functions. */
void _rpc_server_send_inputs(const PoolVector<uint8_t> &p_data);
/* On client rpc functions. */
void _rpc_send_tick_additional_speed(const PoolVector<uint8_t> &p_data);
/* On puppet rpc functions. */
void _rpc_doll_notify_sync_pause(uint32_t p_epoch);
void _rpc_doll_send_epoch_batch(const PoolVector<uint8_t> &p_data);
void process(real_t p_delta);
void player_set_has_new_input(bool p_has);
bool player_has_new_input() const;
void __on_sync_paused();
private:
virtual void _notification(int p_what);
};
struct FrameSnapshot {
uint32_t id;
BitArray inputs_buffer;
uint32_t buffer_size_bit;
uint32_t similarity;
bool operator==(const FrameSnapshot &p_other) const {
return p_other.id == id;
}
};
struct Controller {
NetworkedController *node;
Controller(NetworkedController *p_node) :
node(p_node) {}
virtual ~Controller() = default;
virtual void ready() {}
virtual uint32_t get_current_input_id() const = 0;
virtual void clear_peers() {}
virtual void activate_peer(int p_peer) {}
virtual void deactivate_peer(int p_peer) {}
};
struct ServerController : public Controller {
struct Peer {
Peer() = default;
Peer(int p_peer) :
peer(p_peer) {}
int peer = 0;
bool active = true;
real_t update_rate_factor = 1.0;
int collect_timer = 0; // In frames
int collect_threshold = 0; // In frames
LocalVector<PoolVector<uint8_t>> epoch_batch;
uint32_t batch_size = 0;
};
uint32_t current_input_buffer_id = UINT32_MAX;
uint32_t ghost_input_count = 0;
uint32_t last_sent_state_input_id = 0;
real_t client_tick_additional_speed = 0.0;
real_t additional_speed_notif_timer = 0.0;
std::deque<FrameSnapshot> snapshots;
bool streaming_paused = false;
bool enabled = true;
uint32_t input_arrival_time = UINT32_MAX;
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
/// Used to sync the dolls.
LocalVector<Peer> peers;
DataBuffer epoch_state_data_cache;
uint32_t epoch = 0;
bool is_epoch_important = false;
real_t batch_sync_timer = 0.0;
ServerController(
NetworkedController *p_node,
int p_traced_frames);
void process(real_t p_delta);
uint32_t last_known_input() const;
virtual uint32_t get_current_input_id() const;
void set_enabled(bool p_enable);
virtual void clear_peers();
virtual void activate_peer(int p_peer);
virtual void deactivate_peer(int p_peer);
void receive_inputs(const PoolVector<uint8_t> &p_data);
int get_inputs_count() const;
/// Fetch the next inputs, returns true if the input is new.
bool fetch_next_input();
void notify_send_state();
void doll_sync(real_t p_delta);
/// This function updates the `tick_additional_speed` so that the `frames_inputs`
/// size is enough to reduce the missing packets to 0.
///
/// When the internet connection is bad, the packets need more time to arrive.
/// To heal this problem, the server tells the client to speed up a little bit
/// so it send the inputs a bit earlier than the usual.
///
/// If the `frames_inputs` size is too big the input lag between the client and
/// the server is artificial and no more dependent on the internet. For this
/// reason the server tells the client to slowdown so to keep the `frames_inputs`
/// size moderate to the needs.
void calculates_player_tick_rate(real_t p_delta);
void adjust_player_tick_rate(real_t p_delta);
uint32_t find_peer(int p_peer) const;
};
struct PlayerController : public Controller {
uint32_t current_input_id;
uint32_t input_buffers_counter;
real_t time_bank;
real_t tick_additional_speed;
bool streaming_paused = false;
std::deque<FrameSnapshot> frames_snapshot;
LocalVector<uint8_t> cached_packet_data;
PlayerController(NetworkedController *p_node);
void process(real_t p_delta);
int calculates_sub_ticks(real_t p_delta, real_t p_iteration_per_seconds);
int notify_input_checked(uint32_t p_input_id);
uint32_t last_known_input() const;
uint32_t get_stored_input_id(int p_i) const;
virtual uint32_t get_current_input_id() const;
bool process_instant(int p_i, real_t p_delta);
real_t get_pretended_delta(real_t p_iteration_per_second) const;
void store_input_buffer(uint32_t p_id);
/// Sends an unreliable packet to the server, containing a packed array of
/// frame snapshots.
void send_frame_input_buffer_to_server();
bool can_accept_new_inputs() const;
};
/// The doll controller is kind of special controller, it's using a
/// `ServerController` + `MastertController`.
/// The `DollController` receives inputs from the client as the server does,
/// and fetch them exactly like the server.
/// After the execution of the inputs, the puppet start to act like the player,
/// because it wait the player status from the server to correct its motion.
///
/// There are some extra features available that allow the doll to stay in sync
/// with the server execution (see `soft_reset_to_server_state`) and the possibility
/// for the server to stop the data streaming.
struct DollController : public Controller {
Interpolator interpolator;
real_t additional_speed = 0.0;
uint32_t current_epoch = UINT32_MAX;
real_t advancing_epoch = 0.0;
uint32_t missing_epochs = 0;
// Any received epoch prior to this one is discarded.
uint32_t paused_epoch = 0;
// Used to track the time taken for the next batch to arrive.
uint32_t batch_receiver_timer = UINT32_MAX;
/// Used to track how network is performing.
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
DollController(NetworkedController *p_node);
virtual void ready();
void process(real_t p_delta);
// TODO consider make this non virtual
virtual uint32_t get_current_input_id() const;
void receive_batch(const PoolVector<uint8_t> &p_data);
uint32_t receive_epoch(const PoolVector<uint8_t> &p_data);
uint32_t next_epoch();
void pause(uint32_t p_epoch);
};
/// This controller is used when the game instance is not a peer of any kind.
/// This controller keeps the workflow as usual so it's possible to use the
/// `NetworkedController` even without network.
struct NoNetController : public Controller {
uint32_t frame_id;
NoNetController(NetworkedController *p_node);
void process(real_t p_delta);
virtual uint32_t get_current_input_id() const;
};
#endif