mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-27 14:17:37 +01:00
531 lines
19 KiB
C++
531 lines
19 KiB
C++
#ifndef NETWORKED_CONTROLLER_H
|
|
#define NETWORKED_CONTROLLER_H
|
|
|
|
/*************************************************************************/
|
|
/* networked_controller.h */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* PANDEMONIUM ENGINE */
|
|
/* https://github.com/Relintai/pandemonium_engine */
|
|
/*************************************************************************/
|
|
/* 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 "net_utilities.h"
|
|
#include <deque>
|
|
|
|
#include "compat_object_id.h"
|
|
|
|
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.
|
|
/// The result of these operations, are guaranteed to be the same accross 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_AUTONOMOUS_SERVER,
|
|
CONTROLLER_TYPE_SERVER,
|
|
CONTROLLER_TYPE_DOLL
|
|
};
|
|
|
|
private:
|
|
/// When `true`, this controller is controlled by the server: All the clients
|
|
/// see it as a `Doll`.
|
|
/// This property is really useful to implement bots (Character controlled by
|
|
/// the AI).
|
|
///
|
|
/// NOTICE: Generally you specify this property on the editor, in addition
|
|
/// it's possible to change this at runtime: this will cause the server to
|
|
/// notify all the clients; so the switch is not immediate. This feature can be
|
|
/// used to switch the Character possession between the AI (Server) and
|
|
/// PlayerController (Client) without the need to re-instantiate the Character.
|
|
bool server_controlled = false;
|
|
|
|
/// 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 = 6;
|
|
|
|
/// Time in seconds between each `tick_speedup` that the server sends to the
|
|
/// client. In ms.
|
|
int tick_speedup_notification_delay = 600;
|
|
|
|
/// 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;
|
|
|
|
/// 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 = 0;
|
|
int max_frames_delay = 7;
|
|
|
|
/// Amount of additional frames produced per second.
|
|
double tick_acceleration = 5.0;
|
|
|
|
/// The doll epoch send rate: in Hz (frames per seconds).
|
|
uint32_t doll_sync_rate = 30;
|
|
|
|
/// 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 = 0;
|
|
int doll_max_frames_delay = 25;
|
|
|
|
/// Sensitivity to network oscillations. The value is in seconds and can be
|
|
/// used to establish the connection quality.
|
|
///
|
|
/// The net sync checks the amount of time for each packet to arrive.
|
|
/// The different it is, the more unreliable the connection is, so virtual latency
|
|
/// is used to smooth the interpolation.
|
|
///
|
|
/// `doll_net_sensitivity` is an amount in seconds used to determine the maximum delta difference
|
|
/// between the packets.
|
|
real_t doll_net_sensitivity = 0.21;
|
|
|
|
/// Max doll interpolation overshot. Unit: normalized percentage.
|
|
real_t doll_interpolation_max_overshot = 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_sync_rate`.
|
|
int doll_connection_stats_frame_span = 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();
|
|
~NetworkedController();
|
|
|
|
void set_server_controlled(bool p_server_controlled);
|
|
bool get_server_controlled() const;
|
|
|
|
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(int p_delay_in_ms);
|
|
int 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_tick_acceleration(double p_acceleration);
|
|
double get_tick_acceleration() const;
|
|
|
|
void set_doll_epoch_collect_rate(int p_rate);
|
|
int get_doll_epoch_collect_rate() const;
|
|
|
|
void set_doll_sync_rate(uint32_t p_rate);
|
|
uint32_t get_doll_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_net_sensitivity(real_t p_sensitivity);
|
|
real_t get_doll_net_sensitivity() const;
|
|
|
|
void set_doll_interpolation_max_overshot(real_t p_speedup);
|
|
real_t get_doll_interpolation_max_overshot() const;
|
|
|
|
void set_doll_connection_stats_frame_span(int p_span);
|
|
int get_doll_connection_stats_frame_span() const;
|
|
|
|
void set_doll_virtual_delay_max_bias(uint32_t p_max_delay);
|
|
uint32_t get_doll_virtual_delay_max_bias() 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() 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();
|
|
|
|
virtual void validate_script_implementation();
|
|
virtual void native_collect_inputs(double p_delta, DataBuffer &r_buffer);
|
|
virtual void native_controller_process(double p_delta, DataBuffer &p_buffer);
|
|
virtual bool native_are_inputs_different(DataBuffer &p_buffer_A, DataBuffer &p_buffer_B);
|
|
virtual uint32_t native_count_input_size(DataBuffer &p_buffer);
|
|
virtual void native_collect_epoch_data(DataBuffer &r_buffer);
|
|
virtual void native_apply_epoch(double p_delta, real_t p_interpolation_alpha, DataBuffer &p_past_buffer, DataBuffer &p_future_buffer);
|
|
|
|
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_networking_initialized() 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 Vector<uint8_t> &p_data);
|
|
|
|
/* On client rpc functions. */
|
|
void _rpc_set_server_controlled(bool p_server_controlled);
|
|
void _rpc_notify_fps_acceleration(const Vector<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 Vector<uint8_t> &p_data);
|
|
|
|
void process(double p_delta);
|
|
|
|
void player_set_has_new_input(bool p_has);
|
|
bool player_has_new_input() const;
|
|
|
|
void __on_sync_paused();
|
|
|
|
protected:
|
|
void _notification(int p_what);
|
|
void notify_controller_reset();
|
|
};
|
|
|
|
struct FrameSnapshot {
|
|
uint32_t id;
|
|
BitArray inputs_buffer;
|
|
uint32_t buffer_size_bit;
|
|
uint32_t similarity;
|
|
/// Local timestamp.
|
|
uint32_t received_timestamp;
|
|
|
|
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 doll_sync_rate_factor = 1.0;
|
|
real_t doll_sync_timer = 0.0;
|
|
real_t doll_sync_time_threshold = 0.0;
|
|
};
|
|
|
|
uint32_t current_input_buffer_id = UINT32_MAX;
|
|
uint32_t ghost_input_count = 0;
|
|
uint32_t last_sent_state_input_id = 0;
|
|
uint32_t additional_fps_notif_timer = 0;
|
|
std::deque<FrameSnapshot> snapshots;
|
|
bool streaming_paused = false;
|
|
bool enabled = true;
|
|
|
|
uint32_t previous_frame_received_timestamp = UINT32_MAX;
|
|
NetUtility::StatisticalRingBuffer<uint32_t> network_watcher;
|
|
NetUtility::StatisticalRingBuffer<int> consecutive_input_watcher;
|
|
|
|
/// Used to sync the dolls.
|
|
LocalVector<Peer> peers;
|
|
DataBuffer epoch_state_data_cache;
|
|
uint32_t epoch = 0;
|
|
bool is_epoch_important = false;
|
|
|
|
ServerController(
|
|
NetworkedController *p_node,
|
|
int p_traced_frames);
|
|
|
|
void process(double p_delta);
|
|
uint32_t last_known_input() const;
|
|
virtual uint32_t get_current_input_id() const override;
|
|
|
|
void set_enabled(bool p_enable);
|
|
|
|
virtual void clear_peers() override;
|
|
virtual void activate_peer(int p_peer) override;
|
|
virtual void deactivate_peer(int p_peer) override;
|
|
|
|
virtual void receive_inputs(const Vector<uint8_t> &p_data);
|
|
virtual int get_inputs_count() const;
|
|
|
|
/// Fetch the next inputs, returns true if the input is new.
|
|
virtual bool fetch_next_input(real_t p_delta);
|
|
|
|
void set_frame_input(const FrameSnapshot &p_frame_snapshot);
|
|
|
|
void notify_send_state();
|
|
|
|
void doll_sync(real_t p_delta);
|
|
|
|
/// This function updates the `tick_additional_fps` 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.
|
|
virtual void adjust_player_tick_rate(double p_delta);
|
|
|
|
uint32_t find_peer(int p_peer) const;
|
|
};
|
|
|
|
struct AutonomousServerController : public ServerController {
|
|
AutonomousServerController(
|
|
NetworkedController *p_node);
|
|
|
|
virtual void receive_inputs(const Vector<uint8_t> &p_data) override;
|
|
virtual int get_inputs_count() const override;
|
|
virtual bool fetch_next_input(real_t p_delta) override;
|
|
virtual void adjust_player_tick_rate(double p_delta) override;
|
|
};
|
|
|
|
struct PlayerController : public Controller {
|
|
uint32_t current_input_id;
|
|
uint32_t input_buffers_counter;
|
|
double time_bank;
|
|
double acceleration_fps_speed = 0.0;
|
|
double acceleration_fps_timer = 1.0;
|
|
bool streaming_paused = false;
|
|
double pretended_delta = 1.0;
|
|
|
|
std::deque<FrameSnapshot> frames_snapshot;
|
|
LocalVector<uint8_t> cached_packet_data;
|
|
|
|
PlayerController(NetworkedController *p_node);
|
|
|
|
void process(double p_delta);
|
|
/// Returns the amount of frames to process for this frame.
|
|
int calculates_sub_ticks(const double p_delta, const double 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 override;
|
|
|
|
bool process_instant(int p_i, real_t p_delta);
|
|
|
|
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 {
|
|
real_t interpolation_alpha = 0.0;
|
|
real_t interpolation_time_window = 0.0;
|
|
|
|
uint32_t current_epoch = 0;
|
|
|
|
uint32_t past_epoch = 0;
|
|
DataBuffer past_epoch_buffer;
|
|
|
|
uint32_t future_epoch = 0;
|
|
DataBuffer future_epoch_buffer;
|
|
|
|
// 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 epoch_received_timestamp = UINT32_MAX;
|
|
real_t next_epoch_expected_in = 0.0;
|
|
/// Used to track how network is performing.
|
|
NetUtility::StatisticalRingBuffer<real_t> network_watcher;
|
|
|
|
DollController(NetworkedController *p_node);
|
|
|
|
virtual void ready() override;
|
|
void process(double p_delta);
|
|
// TODO consider make this non virtual
|
|
virtual uint32_t get_current_input_id() const override;
|
|
|
|
void receive_epoch(const Vector<uint8_t> &p_data);
|
|
|
|
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(double p_delta);
|
|
virtual uint32_t get_current_input_id() const override;
|
|
};
|
|
|
|
#endif
|