2018-05-16 19:19:33 +02:00
/*************************************************************************/
/* multiplayer_api.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
2021-01-01 20:13:46 +01:00
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2018-05-16 19:19:33 +02:00
/* */
/* 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. */
/*************************************************************************/
2018-09-11 18:13:45 +02:00
# include "multiplayer_api.h"
2020-02-27 03:30:20 +01:00
# include "core/debugger/engine_debugger.h"
2018-03-03 18:28:49 +01:00
# include "core/io/marshalls.h"
2021-08-09 22:46:23 +02:00
# include "core/io/multiplayer_replicator.h"
2018-03-03 18:28:49 +01:00
# include "scene/main/node.h"
2020-05-12 17:01:17 +02:00
2020-02-12 11:51:50 +01:00
# include <stdint.h>
2018-03-03 18:28:49 +01:00
2019-09-01 18:38:58 +02:00
# ifdef DEBUG_ENABLED
# include "core/os/os.h"
# endif
2021-05-26 14:07:57 +02:00
String _get_rpc_md5 ( const Node * p_node ) {
String rpc_list ;
const Vector < MultiplayerAPI : : RPCConfig > node_config = p_node - > get_node_rpc_methods ( ) ;
for ( int i = 0 ; i < node_config . size ( ) ; i + + ) {
rpc_list + = String ( node_config [ i ] . name ) ;
}
if ( p_node - > get_script_instance ( ) ) {
const Vector < MultiplayerAPI : : RPCConfig > script_config = p_node - > get_script_instance ( ) - > get_rpc_methods ( ) ;
for ( int i = 0 ; i < script_config . size ( ) ; i + + ) {
rpc_list + = String ( script_config [ i ] . name ) ;
}
}
return rpc_list . md5_text ( ) ;
}
const MultiplayerAPI : : RPCConfig _get_rpc_config ( const Node * p_node , const StringName & p_method , uint16_t & r_id ) {
const Vector < MultiplayerAPI : : RPCConfig > node_config = p_node - > get_node_rpc_methods ( ) ;
for ( int i = 0 ; i < node_config . size ( ) ; i + + ) {
if ( node_config [ i ] . name = = p_method ) {
2021-07-29 00:00:30 +02:00
r_id = ( ( uint16_t ) i ) | ( 1 < < 15 ) ;
2021-05-26 14:07:57 +02:00
return node_config [ i ] ;
}
}
if ( p_node - > get_script_instance ( ) ) {
const Vector < MultiplayerAPI : : RPCConfig > script_config = p_node - > get_script_instance ( ) - > get_rpc_methods ( ) ;
for ( int i = 0 ; i < script_config . size ( ) ; i + + ) {
if ( script_config [ i ] . name = = p_method ) {
r_id = ( uint16_t ) i ;
return script_config [ i ] ;
}
}
}
return MultiplayerAPI : : RPCConfig ( ) ;
}
const MultiplayerAPI : : RPCConfig _get_rpc_config_by_id ( Node * p_node , uint16_t p_id ) {
Vector < MultiplayerAPI : : RPCConfig > config ;
uint16_t id = p_id ;
if ( id & ( 1 < < 15 ) ) {
id = id & ~ ( 1 < < 15 ) ;
config = p_node - > get_node_rpc_methods ( ) ;
2021-07-09 14:40:25 +02:00
} else if ( p_node - > get_script_instance ( ) ) {
2021-05-26 14:07:57 +02:00
config = p_node - > get_script_instance ( ) - > get_rpc_methods ( ) ;
}
if ( id < config . size ( ) ) {
2021-07-29 00:00:30 +02:00
return config [ id ] ;
2021-05-26 14:07:57 +02:00
}
return MultiplayerAPI : : RPCConfig ( ) ;
}
2019-04-05 18:18:08 +02:00
_FORCE_INLINE_ bool _can_call_mode ( Node * p_node , MultiplayerAPI : : RPCMode mode , int p_remote_id ) {
2018-05-13 07:07:56 +02:00
switch ( mode ) {
case MultiplayerAPI : : RPC_MODE_DISABLED : {
return false ;
} break ;
2021-06-24 10:28:15 +02:00
case MultiplayerAPI : : RPC_MODE_REMOTE : {
2018-05-13 07:07:56 +02:00
return true ;
} break ;
case MultiplayerAPI : : RPC_MODE_MASTER : {
2019-04-05 18:18:08 +02:00
return p_node - > is_network_master ( ) ;
2018-05-13 07:07:56 +02:00
} break ;
2018-09-14 21:59:47 +02:00
case MultiplayerAPI : : RPC_MODE_PUPPET : {
2019-04-05 18:18:08 +02:00
return ! p_node - > is_network_master ( ) & & p_remote_id = = p_node - > get_network_master ( ) ;
2018-05-13 07:07:56 +02:00
} break ;
}
return false ;
}
2018-03-03 18:28:49 +01:00
void MultiplayerAPI : : poll ( ) {
2021-07-12 16:11:05 +02:00
if ( ! network_peer . is_valid ( ) | | network_peer - > get_connection_status ( ) = = MultiplayerPeer : : CONNECTION_DISCONNECTED ) {
2018-03-03 18:28:49 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
network_peer - > poll ( ) ;
2020-05-14 16:41:43 +02:00
if ( ! network_peer . is_valid ( ) ) { // It's possible that polling might have resulted in a disconnection, so check here.
2018-03-03 18:28:49 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
while ( network_peer - > get_available_packet_count ( ) ) {
int sender = network_peer - > get_packet_peer ( ) ;
const uint8_t * packet ;
int len ;
Error err = network_peer - > get_packet ( & packet , len ) ;
if ( err ! = OK ) {
ERR_PRINT ( " Error getting packet! " ) ;
2020-01-21 20:46:32 +01:00
break ; // Something is wrong!
2018-03-03 18:28:49 +01:00
}
rpc_sender_id = sender ;
_process_packet ( sender , packet , len ) ;
rpc_sender_id = 0 ;
if ( ! network_peer . is_valid ( ) ) {
2018-09-25 17:44:26 +02:00
break ; // It's also possible that a packet or RPC caused a disconnection, so also check here.
2018-03-03 18:28:49 +01:00
}
}
}
void MultiplayerAPI : : clear ( ) {
2021-08-09 22:46:23 +02:00
replicator - > clear ( ) ;
2018-03-03 18:28:49 +01:00
connected_peers . clear ( ) ;
path_get_cache . clear ( ) ;
path_send_cache . clear ( ) ;
2019-01-15 10:35:26 +01:00
packet_cache . clear ( ) ;
2018-03-03 18:28:49 +01:00
last_send_cache_id = 1 ;
}
void MultiplayerAPI : : set_root_node ( Node * p_node ) {
root_node = p_node ;
}
2020-06-01 10:34:15 +02:00
Node * MultiplayerAPI : : get_root_node ( ) {
return root_node ;
}
2021-07-12 16:11:05 +02:00
void MultiplayerAPI : : set_network_peer ( const Ref < MultiplayerPeer > & p_peer ) {
2020-05-14 16:41:43 +02:00
if ( p_peer = = network_peer ) {
2020-05-10 12:56:01 +02:00
return ; // Nothing to do
2020-05-14 16:41:43 +02:00
}
2019-01-15 11:14:51 +01:00
2021-07-12 16:11:05 +02:00
ERR_FAIL_COND_MSG ( p_peer . is_valid ( ) & & p_peer - > get_connection_status ( ) = = MultiplayerPeer : : CONNECTION_DISCONNECTED ,
" Supplied MultiplayerPeer must be connecting or connected. " ) ;
2020-01-03 13:34:51 +01:00
2018-03-03 18:28:49 +01:00
if ( network_peer . is_valid ( ) ) {
2020-02-21 18:28:45 +01:00
network_peer - > disconnect ( " peer_connected " , callable_mp ( this , & MultiplayerAPI : : _add_peer ) ) ;
network_peer - > disconnect ( " peer_disconnected " , callable_mp ( this , & MultiplayerAPI : : _del_peer ) ) ;
network_peer - > disconnect ( " connection_succeeded " , callable_mp ( this , & MultiplayerAPI : : _connected_to_server ) ) ;
network_peer - > disconnect ( " connection_failed " , callable_mp ( this , & MultiplayerAPI : : _connection_failed ) ) ;
network_peer - > disconnect ( " server_disconnected " , callable_mp ( this , & MultiplayerAPI : : _server_disconnected ) ) ;
2018-03-03 18:28:49 +01:00
clear ( ) ;
}
network_peer = p_peer ;
if ( network_peer . is_valid ( ) ) {
2020-02-21 18:28:45 +01:00
network_peer - > connect ( " peer_connected " , callable_mp ( this , & MultiplayerAPI : : _add_peer ) ) ;
network_peer - > connect ( " peer_disconnected " , callable_mp ( this , & MultiplayerAPI : : _del_peer ) ) ;
network_peer - > connect ( " connection_succeeded " , callable_mp ( this , & MultiplayerAPI : : _connected_to_server ) ) ;
network_peer - > connect ( " connection_failed " , callable_mp ( this , & MultiplayerAPI : : _connection_failed ) ) ;
network_peer - > connect ( " server_disconnected " , callable_mp ( this , & MultiplayerAPI : : _server_disconnected ) ) ;
2018-03-03 18:28:49 +01:00
}
}
2021-07-12 16:11:05 +02:00
Ref < MultiplayerPeer > MultiplayerAPI : : get_network_peer ( ) const {
2018-03-03 18:28:49 +01:00
return network_peer ;
}
2020-02-27 03:30:20 +01:00
# ifdef DEBUG_ENABLED
void _profile_node_data ( const String & p_what , ObjectID p_id ) {
if ( EngineDebugger : : is_profiling ( " multiplayer " ) ) {
Array values ;
values . push_back ( " node " ) ;
values . push_back ( p_id ) ;
values . push_back ( p_what ) ;
EngineDebugger : : profiler_add_frame_data ( " multiplayer " , values ) ;
}
}
2020-05-14 14:29:06 +02:00
2020-02-27 03:30:20 +01:00
void _profile_bandwidth_data ( const String & p_inout , int p_size ) {
if ( EngineDebugger : : is_profiling ( " multiplayer " ) ) {
Array values ;
values . push_back ( " bandwidth " ) ;
values . push_back ( p_inout ) ;
values . push_back ( OS : : get_singleton ( ) - > get_ticks_msec ( ) ) ;
values . push_back ( p_size ) ;
EngineDebugger : : profiler_add_frame_data ( " multiplayer " , values ) ;
}
}
# endif
2020-02-27 08:19:59 +01:00
// Returns the packet size stripping the node path added when the node is not yet cached.
int get_packet_len ( uint32_t p_node_target , int p_packet_len ) {
if ( p_node_target & 0x80000000 ) {
int ofs = p_node_target & 0x7FFFFFFF ;
return p_packet_len - ( p_packet_len - ofs ) ;
} else {
return p_packet_len ;
}
}
2018-03-03 18:28:49 +01:00
void MultiplayerAPI : : _process_packet ( int p_from , const uint8_t * p_packet , int p_packet_len ) {
2020-04-02 01:20:12 +02:00
ERR_FAIL_COND_MSG ( root_node = = nullptr , " Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it. " ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( p_packet_len < 1 , " Invalid packet received. Size too small. " ) ;
2018-03-03 18:28:49 +01:00
2019-09-01 18:38:58 +02:00
# ifdef DEBUG_ENABLED
2020-02-27 03:30:20 +01:00
_profile_bandwidth_data ( " in " , p_packet_len ) ;
2019-09-01 18:38:58 +02:00
# endif
2020-02-12 11:51:50 +01:00
// Extract the `packet_type` from the LSB three bits:
uint8_t packet_type = p_packet [ 0 ] & 7 ;
2018-03-03 18:28:49 +01:00
switch ( packet_type ) {
case NETWORK_COMMAND_SIMPLIFY_PATH : {
_process_simplify_path ( p_from , p_packet , p_packet_len ) ;
} break ;
case NETWORK_COMMAND_CONFIRM_PATH : {
_process_confirm_path ( p_from , p_packet , p_packet_len ) ;
} break ;
2021-05-26 14:07:57 +02:00
case NETWORK_COMMAND_REMOTE_CALL : {
2020-02-12 11:51:50 +01:00
// Extract packet meta
int packet_min_size = 1 ;
int name_id_offset = 1 ;
ERR_FAIL_COND_MSG ( p_packet_len < packet_min_size , " Invalid packet received. Size too small. " ) ;
// Compute the meta size, which depends on the compression level.
2020-02-27 08:19:59 +01:00
int node_id_compression = ( p_packet [ 0 ] & 24 ) > > NODE_ID_COMPRESSION_SHIFT ;
int name_id_compression = ( p_packet [ 0 ] & 32 ) > > NAME_ID_COMPRESSION_SHIFT ;
2020-02-12 11:51:50 +01:00
switch ( node_id_compression ) {
case NETWORK_NODE_ID_COMPRESSION_8 :
packet_min_size + = 1 ;
name_id_offset + = 1 ;
break ;
case NETWORK_NODE_ID_COMPRESSION_16 :
packet_min_size + = 2 ;
name_id_offset + = 2 ;
break ;
case NETWORK_NODE_ID_COMPRESSION_32 :
packet_min_size + = 4 ;
name_id_offset + = 4 ;
break ;
default :
ERR_FAIL_MSG ( " Was not possible to extract the node id compression mode. " ) ;
}
switch ( name_id_compression ) {
case NETWORK_NAME_ID_COMPRESSION_8 :
packet_min_size + = 1 ;
break ;
case NETWORK_NAME_ID_COMPRESSION_16 :
packet_min_size + = 2 ;
break ;
default :
ERR_FAIL_MSG ( " Was not possible to extract the name id compression mode. " ) ;
}
ERR_FAIL_COND_MSG ( p_packet_len < packet_min_size , " Invalid packet received. Size too small. " ) ;
2018-03-03 18:28:49 +01:00
2020-02-12 11:51:50 +01:00
uint32_t node_target = 0 ;
switch ( node_id_compression ) {
case NETWORK_NODE_ID_COMPRESSION_8 :
node_target = p_packet [ 1 ] ;
break ;
case NETWORK_NODE_ID_COMPRESSION_16 :
node_target = decode_uint16 ( p_packet + 1 ) ;
break ;
case NETWORK_NODE_ID_COMPRESSION_32 :
node_target = decode_uint32 ( p_packet + 1 ) ;
break ;
default :
// Unreachable, checked before.
CRASH_NOW ( ) ;
}
2020-02-27 08:19:59 +01:00
2020-02-12 11:51:50 +01:00
Node * node = _process_get_node ( p_from , p_packet , node_target , p_packet_len ) ;
2020-04-02 01:20:12 +02:00
ERR_FAIL_COND_MSG ( node = = nullptr , " Invalid packet received. Requested node was not found. " ) ;
2018-03-03 18:28:49 +01:00
2020-02-12 11:51:50 +01:00
uint16_t name_id = 0 ;
switch ( name_id_compression ) {
case NETWORK_NAME_ID_COMPRESSION_8 :
name_id = p_packet [ name_id_offset ] ;
2018-03-03 18:28:49 +01:00
break ;
2020-02-12 11:51:50 +01:00
case NETWORK_NAME_ID_COMPRESSION_16 :
name_id = decode_uint16 ( p_packet + name_id_offset ) ;
break ;
default :
// Unreachable, checked before.
CRASH_NOW ( ) ;
2018-03-03 18:28:49 +01:00
}
2020-02-27 08:19:59 +01:00
const int packet_len = get_packet_len ( node_target , p_packet_len ) ;
2021-05-26 14:07:57 +02:00
_process_rpc ( node , name_id , p_from , p_packet , packet_len , packet_min_size ) ;
2018-03-03 18:28:49 +01:00
} break ;
2018-05-11 21:34:45 +02:00
case NETWORK_COMMAND_RAW : {
_process_raw ( p_from , p_packet , p_packet_len ) ;
} break ;
2021-07-30 02:14:37 +02:00
case NETWORK_COMMAND_SPAWN : {
2021-08-09 22:46:23 +02:00
replicator - > process_spawn_despawn ( p_from , p_packet , p_packet_len , true ) ;
2021-07-30 02:14:37 +02:00
} break ;
case NETWORK_COMMAND_DESPAWN : {
2021-08-09 22:46:23 +02:00
replicator - > process_spawn_despawn ( p_from , p_packet , p_packet_len , false ) ;
2021-07-30 02:14:37 +02:00
} break ;
2018-03-03 18:28:49 +01:00
}
}
2020-02-12 11:51:50 +01:00
Node * MultiplayerAPI : : _process_get_node ( int p_from , const uint8_t * p_packet , uint32_t p_node_target , int p_packet_len ) {
2020-04-02 01:20:12 +02:00
Node * node = nullptr ;
2018-03-03 18:28:49 +01:00
2020-02-12 11:51:50 +01:00
if ( p_node_target & 0x80000000 ) {
2018-09-25 17:44:26 +02:00
// Use full path (not cached yet).
2020-02-12 11:51:50 +01:00
int ofs = p_node_target & 0x7FFFFFFF ;
2018-09-25 17:44:26 +02:00
2020-04-02 01:20:12 +02:00
ERR_FAIL_COND_V_MSG ( ofs > = p_packet_len , nullptr , " Invalid packet received. Size smaller than declared. " ) ;
2018-03-03 18:28:49 +01:00
String paths ;
paths . parse_utf8 ( ( const char * ) & p_packet [ ofs ] , p_packet_len - ofs ) ;
NodePath np = paths ;
node = root_node - > get_node ( np ) ;
2020-05-14 16:41:43 +02:00
if ( ! node ) {
2019-11-06 17:03:04 +01:00
ERR_PRINT ( " Failed to get path from RPC: " + String ( np ) + " . " ) ;
2020-05-14 16:41:43 +02:00
}
2021-08-09 22:46:23 +02:00
return node ;
2018-03-03 18:28:49 +01:00
} else {
2018-09-25 17:44:26 +02:00
// Use cached path.
2021-08-09 22:46:23 +02:00
return get_cached_node ( p_from , p_node_target ) ;
2018-03-03 18:28:49 +01:00
}
}
2020-02-12 11:51:50 +01:00
void MultiplayerAPI : : _process_rpc ( Node * p_node , const uint16_t p_rpc_method_id , int p_from , const uint8_t * p_packet , int p_packet_len , int p_offset ) {
2020-02-27 08:19:59 +01:00
ERR_FAIL_COND_MSG ( p_offset > p_packet_len , " Invalid packet received. Size too small. " ) ;
2018-03-03 18:28:49 +01:00
2018-09-25 17:44:26 +02:00
// Check that remote can call the RPC on this node.
2021-05-26 14:07:57 +02:00
const RPCConfig config = _get_rpc_config_by_id ( p_node , p_rpc_method_id ) ;
ERR_FAIL_COND ( config . name = = StringName ( ) ) ;
2018-09-25 17:44:26 +02:00
2021-05-26 14:07:57 +02:00
bool can_call = _can_call_mode ( p_node , config . rpc_mode , p_from ) ;
ERR_FAIL_COND_MSG ( ! can_call , " RPC ' " + String ( config . name ) + " ' is not allowed on node " + p_node - > get_path ( ) + " from: " + itos ( p_from ) + " . Mode is " + itos ( ( int ) config . rpc_mode ) + " , master is " + itos ( p_node - > get_network_master ( ) ) + " . " ) ;
2018-05-13 07:07:56 +02:00
2020-02-27 08:19:59 +01:00
int argc = 0 ;
bool byte_only = false ;
const bool byte_only_or_no_args = ( ( p_packet [ 0 ] & 64 ) > > BYTE_ONLY_OR_NO_ARGS_SHIFT ) = = 1 ;
if ( byte_only_or_no_args ) {
if ( p_offset < p_packet_len ) {
// This packet contains only bytes.
argc = 1 ;
byte_only = true ;
} else {
// This rpc calls a method without parameters.
}
} else {
// Normal variant, takes the argument count from the packet.
ERR_FAIL_COND_MSG ( p_offset > = p_packet_len , " Invalid packet received. Size too small. " ) ;
argc = p_packet [ p_offset ] ;
p_offset + = 1 ;
}
2018-03-03 18:28:49 +01:00
Vector < Variant > args ;
Vector < const Variant * > argp ;
args . resize ( argc ) ;
argp . resize ( argc ) ;
2019-09-01 18:38:58 +02:00
# ifdef DEBUG_ENABLED
2020-02-27 03:30:20 +01:00
_profile_node_data ( " in_rpc " , p_node - > get_instance_id ( ) ) ;
2019-09-01 18:38:58 +02:00
# endif
2020-02-27 08:19:59 +01:00
if ( byte_only ) {
Vector < uint8_t > pure_data ;
const int len = p_packet_len - p_offset ;
pure_data . resize ( len ) ;
memcpy ( pure_data . ptrw ( ) , & p_packet [ p_offset ] , len ) ;
args . write [ 0 ] = pure_data ;
argp . write [ 0 ] = & args [ 0 ] ;
p_offset + = len ;
} else {
for ( int i = 0 ; i < argc ; i + + ) {
ERR_FAIL_COND_MSG ( p_offset > = p_packet_len , " Invalid packet received. Size too small. " ) ;
2018-09-25 17:44:26 +02:00
2020-02-27 08:19:59 +01:00
int vlen ;
2021-08-09 22:46:23 +02:00
Error err = decode_and_decompress_variant ( args . write [ i ] , & p_packet [ p_offset ] , p_packet_len - p_offset , & vlen ) ;
2020-02-27 08:19:59 +01:00
ERR_FAIL_COND_MSG ( err ! = OK , " Invalid packet received. Unable to decode RPC argument. " ) ;
2018-09-25 17:44:26 +02:00
2020-02-27 08:19:59 +01:00
argp . write [ i ] = & args [ i ] ;
p_offset + = vlen ;
}
2018-03-03 18:28:49 +01:00
}
2020-02-19 20:27:19 +01:00
Callable : : CallError ce ;
2018-03-03 18:28:49 +01:00
2021-05-26 14:07:57 +02:00
p_node - > call ( config . name , ( const Variant * * ) argp . ptr ( ) , argc , ce ) ;
2020-02-19 20:27:19 +01:00
if ( ce . error ! = Callable : : CallError : : CALL_OK ) {
2021-05-26 14:07:57 +02:00
String error = Variant : : get_call_error_text ( p_node , config . name , ( const Variant * * ) argp . ptr ( ) , argc , ce ) ;
2018-03-03 18:28:49 +01:00
error = " RPC - " + error ;
2019-11-06 17:03:04 +01:00
ERR_PRINT ( error ) ;
2018-03-03 18:28:49 +01:00
}
}
void MultiplayerAPI : : _process_simplify_path ( int p_from , const uint8_t * p_packet , int p_packet_len ) {
2020-02-12 11:51:50 +01:00
ERR_FAIL_COND_MSG ( p_packet_len < 38 , " Invalid packet received. Size too small. " ) ;
int ofs = 1 ;
String methods_md5 ;
methods_md5 . parse_utf8 ( ( const char * ) ( p_packet + ofs ) , 32 ) ;
ofs + = 33 ;
int id = decode_uint32 ( & p_packet [ ofs ] ) ;
ofs + = 4 ;
2018-03-03 18:28:49 +01:00
String paths ;
2020-02-12 11:51:50 +01:00
paths . parse_utf8 ( ( const char * ) ( p_packet + ofs ) , p_packet_len - ofs ) ;
2018-03-03 18:28:49 +01:00
NodePath path = paths ;
if ( ! path_get_cache . has ( p_from ) ) {
path_get_cache [ p_from ] = PathGetCache ( ) ;
}
2020-02-12 11:51:50 +01:00
Node * node = root_node - > get_node ( path ) ;
2020-04-02 01:20:12 +02:00
ERR_FAIL_COND ( node = = nullptr ) ;
2021-05-26 14:07:57 +02:00
const bool valid_rpc_checksum = _get_rpc_md5 ( node ) = = methods_md5 ;
2020-02-12 11:51:50 +01:00
if ( valid_rpc_checksum = = false ) {
ERR_PRINT ( " The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path ) ;
}
2018-03-03 18:28:49 +01:00
PathGetCache : : NodeInfo ni ;
ni . path = path ;
path_get_cache [ p_from ] . nodes [ id ] = ni ;
2018-09-25 17:44:26 +02:00
// Encode path to send ack.
2018-03-03 18:28:49 +01:00
CharString pname = String ( path ) . utf8 ( ) ;
2020-04-02 01:20:12 +02:00
int len = encode_cstring ( pname . get_data ( ) , nullptr ) ;
2018-03-03 18:28:49 +01:00
Vector < uint8_t > packet ;
2020-02-12 11:51:50 +01:00
packet . resize ( 1 + 1 + len ) ;
2018-07-25 03:11:03 +02:00
packet . write [ 0 ] = NETWORK_COMMAND_CONFIRM_PATH ;
2020-02-12 11:51:50 +01:00
packet . write [ 1 ] = valid_rpc_checksum ;
encode_cstring ( pname . get_data ( ) , & packet . write [ 2 ] ) ;
2018-03-03 18:28:49 +01:00
2021-07-27 12:06:48 +02:00
network_peer - > set_transfer_channel ( 0 ) ;
2021-07-12 16:11:05 +02:00
network_peer - > set_transfer_mode ( MultiplayerPeer : : TRANSFER_MODE_RELIABLE ) ;
2018-03-03 18:28:49 +01:00
network_peer - > set_target_peer ( p_from ) ;
network_peer - > put_packet ( packet . ptr ( ) , packet . size ( ) ) ;
}
void MultiplayerAPI : : _process_confirm_path ( int p_from , const uint8_t * p_packet , int p_packet_len ) {
2020-02-12 11:51:50 +01:00
ERR_FAIL_COND_MSG ( p_packet_len < 3 , " Invalid packet received. Size too small. " ) ;
const bool valid_rpc_checksum = p_packet [ 1 ] ;
2018-05-11 21:34:45 +02:00
2018-03-03 18:28:49 +01:00
String paths ;
2020-02-12 11:51:50 +01:00
paths . parse_utf8 ( ( const char * ) & p_packet [ 2 ] , p_packet_len - 2 ) ;
2018-03-03 18:28:49 +01:00
NodePath path = paths ;
2020-02-12 11:51:50 +01:00
if ( valid_rpc_checksum = = false ) {
ERR_PRINT ( " The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path ) ;
}
2018-03-03 18:28:49 +01:00
PathSentCache * psc = path_send_cache . getptr ( path ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( ! psc , " Invalid packet received. Tries to confirm a path which was not found in cache. " ) ;
2018-03-03 18:28:49 +01:00
Map < int , bool > : : Element * E = psc - > confirmed_peers . find ( p_from ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( ! E , " Invalid packet received. Source peer was not found in cache for the given path. " ) ;
2018-03-03 18:28:49 +01:00
E - > get ( ) = true ;
}
2020-02-12 11:51:50 +01:00
bool MultiplayerAPI : : _send_confirm_path ( Node * p_node , NodePath p_path , PathSentCache * psc , int p_target ) {
2018-03-03 18:28:49 +01:00
bool has_all_peers = true ;
2018-09-25 17:44:26 +02:00
List < int > peers_to_add ; // If one is missing, take note to add it.
2018-03-03 18:28:49 +01:00
for ( Set < int > : : Element * E = connected_peers . front ( ) ; E ; E = E - > next ( ) ) {
2020-05-14 16:41:43 +02:00
if ( p_target < 0 & & E - > get ( ) = = - p_target ) {
2018-09-25 17:44:26 +02:00
continue ; // Continue, excluded.
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
2020-05-14 16:41:43 +02:00
if ( p_target > 0 & & E - > get ( ) ! = p_target ) {
2018-09-25 17:44:26 +02:00
continue ; // Continue, not for this peer.
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
Map < int , bool > : : Element * F = psc - > confirmed_peers . find ( E - > get ( ) ) ;
2018-10-06 22:20:41 +02:00
if ( ! F | | ! F - > get ( ) ) {
2018-09-25 17:44:26 +02:00
// Path was not cached, or was cached but is unconfirmed.
2018-03-03 18:28:49 +01:00
if ( ! F ) {
2018-09-25 17:44:26 +02:00
// Not cached at all, take note.
2018-03-03 18:28:49 +01:00
peers_to_add . push_back ( E - > get ( ) ) ;
}
has_all_peers = false ;
}
}
2020-02-12 11:51:50 +01:00
if ( peers_to_add . size ( ) > 0 ) {
// Those that need to be added, send a message for this.
2018-03-03 18:28:49 +01:00
2018-09-25 17:44:26 +02:00
// Encode function name.
2020-02-12 11:51:50 +01:00
const CharString path = String ( p_path ) . utf8 ( ) ;
2020-04-02 01:20:12 +02:00
const int path_len = encode_cstring ( path . get_data ( ) , nullptr ) ;
2020-02-12 11:51:50 +01:00
// Extract MD5 from rpc methods list.
2021-05-26 14:07:57 +02:00
const String methods_md5 = _get_rpc_md5 ( p_node ) ;
2020-02-12 11:51:50 +01:00
const int methods_md5_len = 33 ; // 32 + 1 for the `0` that is added by the encoder.
2018-03-03 18:28:49 +01:00
Vector < uint8_t > packet ;
2020-02-12 11:51:50 +01:00
packet . resize ( 1 + 4 + path_len + methods_md5_len ) ;
int ofs = 0 ;
packet . write [ ofs ] = NETWORK_COMMAND_SIMPLIFY_PATH ;
ofs + = 1 ;
ofs + = encode_cstring ( methods_md5 . utf8 ( ) . get_data ( ) , & packet . write [ ofs ] ) ;
ofs + = encode_uint32 ( psc - > id , & packet . write [ ofs ] ) ;
ofs + = encode_cstring ( path . get_data ( ) , & packet . write [ ofs ] ) ;
2018-03-03 18:28:49 +01:00
2021-07-16 05:45:57 +02:00
for ( int & E : peers_to_add ) {
network_peer - > set_target_peer ( E ) ; // To all of you.
2021-07-27 12:06:48 +02:00
network_peer - > set_transfer_channel ( 0 ) ;
2021-07-12 16:11:05 +02:00
network_peer - > set_transfer_mode ( MultiplayerPeer : : TRANSFER_MODE_RELIABLE ) ;
2020-02-12 11:51:50 +01:00
network_peer - > put_packet ( packet . ptr ( ) , packet . size ( ) ) ;
2018-03-03 18:28:49 +01:00
2021-07-16 05:45:57 +02:00
psc - > confirmed_peers . insert ( E , false ) ; // Insert into confirmed, but as false since it was not confirmed.
2020-02-12 11:51:50 +01:00
}
2018-03-03 18:28:49 +01:00
}
return has_all_peers ;
}
2020-02-12 11:51:50 +01:00
// The variant is compressed and encoded; The first byte contains all the meta
// information and the format is:
// - The first LSB 5 bits are used for the variant type.
// - The next two bits are used to store the encoding mode.
// - The most significant is used to store the boolean value.
# define VARIANT_META_TYPE_MASK 0x1F
# define VARIANT_META_EMODE_MASK 0x60
# define VARIANT_META_BOOL_MASK 0x80
# define ENCODE_8 0 << 5
# define ENCODE_16 1 << 5
# define ENCODE_32 2 << 5
# define ENCODE_64 3 << 5
2021-08-09 22:46:23 +02:00
Error MultiplayerAPI : : encode_and_compress_variant ( const Variant & p_variant , uint8_t * r_buffer , int & r_len ) {
2020-02-12 11:51:50 +01:00
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
CRASH_COND ( p_variant . get_type ( ) > VARIANT_META_TYPE_MASK ) ;
uint8_t * buf = r_buffer ;
r_len = 0 ;
uint8_t encode_mode = 0 ;
switch ( p_variant . get_type ( ) ) {
case Variant : : BOOL : {
if ( buf ) {
// We still have 1 free bit in the meta, so let's use it.
buf [ 0 ] = ( p_variant . operator bool ( ) ) ? ( 1 < < 7 ) : 0 ;
buf [ 0 ] | = encode_mode | p_variant . get_type ( ) ;
}
r_len + = 1 ;
} break ;
case Variant : : INT : {
if ( buf ) {
// Reserve the first byte for the meta.
buf + = 1 ;
}
r_len + = 1 ;
int64_t val = p_variant ;
if ( val < = ( int64_t ) INT8_MAX & & val > = ( int64_t ) INT8_MIN ) {
// Use 8 bit
encode_mode = ENCODE_8 ;
if ( buf ) {
buf [ 0 ] = val ;
}
r_len + = 1 ;
} else if ( val < = ( int64_t ) INT16_MAX & & val > = ( int64_t ) INT16_MIN ) {
// Use 16 bit
encode_mode = ENCODE_16 ;
if ( buf ) {
encode_uint16 ( val , buf ) ;
}
r_len + = 2 ;
} else if ( val < = ( int64_t ) INT32_MAX & & val > = ( int64_t ) INT32_MIN ) {
// Use 32 bit
encode_mode = ENCODE_32 ;
if ( buf ) {
encode_uint32 ( val , buf ) ;
}
r_len + = 4 ;
} else {
// Use 64 bit
encode_mode = ENCODE_64 ;
if ( buf ) {
encode_uint64 ( val , buf ) ;
}
r_len + = 8 ;
}
// Store the meta
if ( buf ) {
buf - = 1 ;
buf [ 0 ] = encode_mode | p_variant . get_type ( ) ;
}
} break ;
default :
// Any other case is not yet compressed.
2020-02-12 12:14:18 +01:00
Error err = encode_variant ( p_variant , r_buffer , r_len , allow_object_decoding ) ;
2020-05-14 16:41:43 +02:00
if ( err ! = OK ) {
2020-02-12 11:51:50 +01:00
return err ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
if ( r_buffer ) {
2021-03-12 14:35:16 +01:00
// The first byte is not used by the marshalling, so store the type
2020-02-12 11:51:50 +01:00
// so we know how to decompress and decode this variant.
r_buffer [ 0 ] = p_variant . get_type ( ) ;
}
}
return OK ;
}
2020-05-14 14:29:06 +02:00
2021-08-09 22:46:23 +02:00
Error MultiplayerAPI : : decode_and_decompress_variant ( Variant & r_variant , const uint8_t * p_buffer , int p_len , int * r_len ) {
2020-02-12 11:51:50 +01:00
const uint8_t * buf = p_buffer ;
int len = p_len ;
ERR_FAIL_COND_V ( len < 1 , ERR_INVALID_DATA ) ;
uint8_t type = buf [ 0 ] & VARIANT_META_TYPE_MASK ;
uint8_t encode_mode = buf [ 0 ] & VARIANT_META_EMODE_MASK ;
ERR_FAIL_COND_V ( type > = Variant : : VARIANT_MAX , ERR_INVALID_DATA ) ;
switch ( type ) {
case Variant : : BOOL : {
bool val = ( buf [ 0 ] & VARIANT_META_BOOL_MASK ) > 0 ;
r_variant = val ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
* r_len = 1 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
} break ;
case Variant : : INT : {
buf + = 1 ;
len - = 1 ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
* r_len = 1 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
if ( encode_mode = = ENCODE_8 ) {
// 8 bits.
ERR_FAIL_COND_V ( len < 1 , ERR_INVALID_DATA ) ;
int8_t val = buf [ 0 ] ;
r_variant = val ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
( * r_len ) + = 1 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
} else if ( encode_mode = = ENCODE_16 ) {
// 16 bits.
ERR_FAIL_COND_V ( len < 2 , ERR_INVALID_DATA ) ;
int16_t val = decode_uint16 ( buf ) ;
r_variant = val ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
( * r_len ) + = 2 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
} else if ( encode_mode = = ENCODE_32 ) {
// 32 bits.
ERR_FAIL_COND_V ( len < 4 , ERR_INVALID_DATA ) ;
int32_t val = decode_uint32 ( buf ) ;
r_variant = val ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
( * r_len ) + = 4 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
} else {
// 64 bits.
ERR_FAIL_COND_V ( len < 8 , ERR_INVALID_DATA ) ;
int64_t val = decode_uint64 ( buf ) ;
r_variant = val ;
2020-05-14 16:41:43 +02:00
if ( r_len ) {
2020-02-12 11:51:50 +01:00
( * r_len ) + = 8 ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
}
} break ;
default :
2020-02-12 12:14:18 +01:00
Error err = decode_variant ( r_variant , p_buffer , p_len , r_len , allow_object_decoding ) ;
2020-05-14 16:41:43 +02:00
if ( err ! = OK ) {
2020-02-12 11:51:50 +01:00
return err ;
2020-05-14 16:41:43 +02:00
}
2020-02-12 11:51:50 +01:00
}
return OK ;
}
2021-05-26 14:07:57 +02:00
void MultiplayerAPI : : _send_rpc ( Node * p_from , int p_to , uint16_t p_rpc_id , const RPCConfig & p_config , const StringName & p_name , const Variant * * p_arg , int p_argcount ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( network_peer . is_null ( ) , " Attempt to remote call/set when networking is not active in SceneTree. " ) ;
2018-03-03 18:28:49 +01:00
2021-07-12 16:11:05 +02:00
ERR_FAIL_COND_MSG ( network_peer - > get_connection_status ( ) = = MultiplayerPeer : : CONNECTION_CONNECTING , " Attempt to remote call/set when networking is not connected yet in SceneTree. " ) ;
2018-03-03 18:28:49 +01:00
2021-07-12 16:11:05 +02:00
ERR_FAIL_COND_MSG ( network_peer - > get_connection_status ( ) = = MultiplayerPeer : : CONNECTION_DISCONNECTED , " Attempt to remote call/set when networking is disconnected. " ) ;
2018-03-03 18:28:49 +01:00
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( p_argcount > 255 , " Too many arguments >255. " ) ;
2018-03-03 18:28:49 +01:00
if ( p_to ! = 0 & & ! connected_peers . has ( ABS ( p_to ) ) ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( p_to = = network_peer - > get_unique_id ( ) , " Attempt to remote call/set yourself! unique ID: " + itos ( network_peer - > get_unique_id ( ) ) + " . " ) ;
2018-03-03 18:28:49 +01:00
2019-08-15 04:57:49 +02:00
ERR_FAIL_MSG ( " Attempt to remote call unexisting ID: " + itos ( p_to ) + " . " ) ;
2018-03-03 18:28:49 +01:00
}
NodePath from_path = ( root_node - > get_path ( ) ) . rel_path_to ( p_from - > get_path ( ) ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( from_path . is_empty ( ) , " Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE! " ) ;
2018-03-03 18:28:49 +01:00
2018-09-25 17:44:26 +02:00
// See if the path is cached.
2018-03-03 18:28:49 +01:00
PathSentCache * psc = path_send_cache . getptr ( from_path ) ;
if ( ! psc ) {
2018-09-25 17:44:26 +02:00
// Path is not cached, create.
2018-03-03 18:28:49 +01:00
path_send_cache [ from_path ] = PathSentCache ( ) ;
psc = path_send_cache . getptr ( from_path ) ;
psc - > id = last_send_cache_id + + ;
}
2020-02-12 11:51:50 +01:00
// See if all peers have cached path (if so, call can be fast).
const bool has_all_peers = _send_confirm_path ( p_from , from_path , psc , p_to ) ;
2018-09-25 17:44:26 +02:00
// Create base packet, lots of hardcode because it must be tight.
2018-03-03 18:28:49 +01:00
int ofs = 0 ;
2020-05-10 12:56:01 +02:00
# define MAKE_ROOM(m_amount) \
if ( packet_cache . size ( ) < m_amount ) \
packet_cache . resize ( m_amount ) ;
2018-03-03 18:28:49 +01:00
2020-02-12 11:51:50 +01:00
// Encode meta.
2021-03-12 14:35:16 +01:00
// The meta is composed by a single byte that contains (starting from the least significant bit):
2020-02-12 11:51:50 +01:00
// - `NetworkCommands` in the first three bits.
// - `NetworkNodeIdCompression` in the next 2 bits.
// - `NetworkNameIdCompression` in the next 1 bit.
2020-02-27 08:19:59 +01:00
// - `byte_only_or_no_args` in the next 1 bit.
// - So we still have the last bit free!
2021-05-26 14:07:57 +02:00
uint8_t command_type = NETWORK_COMMAND_REMOTE_CALL ;
2020-02-12 11:51:50 +01:00
uint8_t node_id_compression = UINT8_MAX ;
uint8_t name_id_compression = UINT8_MAX ;
2020-02-27 08:19:59 +01:00
bool byte_only_or_no_args = false ;
2020-02-12 11:51:50 +01:00
2018-03-03 18:28:49 +01:00
MAKE_ROOM ( 1 ) ;
2020-02-12 11:51:50 +01:00
// The meta is composed along the way, so just set 0 for now.
packet_cache . write [ 0 ] = 0 ;
2018-03-03 18:28:49 +01:00
ofs + = 1 ;
2020-02-12 11:51:50 +01:00
// Encode Node ID.
if ( has_all_peers ) {
// Compress the node ID only if all the target peers already know it.
if ( psc - > id > = 0 & & psc - > id < = 255 ) {
// We can encode the id in 1 byte
node_id_compression = NETWORK_NODE_ID_COMPRESSION_8 ;
MAKE_ROOM ( ofs + 1 ) ;
packet_cache . write [ ofs ] = static_cast < uint8_t > ( psc - > id ) ;
ofs + = 1 ;
} else if ( psc - > id > = 0 & & psc - > id < = 65535 ) {
// We can encode the id in 2 bytes
node_id_compression = NETWORK_NODE_ID_COMPRESSION_16 ;
MAKE_ROOM ( ofs + 2 ) ;
encode_uint16 ( static_cast < uint16_t > ( psc - > id ) , & ( packet_cache . write [ ofs ] ) ) ;
ofs + = 2 ;
} else {
// Too big, let's use 4 bytes.
node_id_compression = NETWORK_NODE_ID_COMPRESSION_32 ;
MAKE_ROOM ( ofs + 4 ) ;
encode_uint32 ( psc - > id , & ( packet_cache . write [ ofs ] ) ) ;
ofs + = 4 ;
}
} else {
2021-03-12 14:35:16 +01:00
// The targets don't know the node yet, so we need to use 32 bits int.
2020-02-12 11:51:50 +01:00
node_id_compression = NETWORK_NODE_ID_COMPRESSION_32 ;
MAKE_ROOM ( ofs + 4 ) ;
encode_uint32 ( psc - > id , & ( packet_cache . write [ ofs ] ) ) ;
ofs + = 4 ;
}
2018-03-03 18:28:49 +01:00
2021-05-26 14:07:57 +02:00
// Encode method ID
if ( p_rpc_id < = UINT8_MAX ) {
// The ID fits in 1 byte
name_id_compression = NETWORK_NAME_ID_COMPRESSION_8 ;
MAKE_ROOM ( ofs + 1 ) ;
packet_cache . write [ ofs ] = static_cast < uint8_t > ( p_rpc_id ) ;
ofs + = 1 ;
2018-03-03 18:28:49 +01:00
} else {
2021-05-26 14:07:57 +02:00
// The ID is larger, let's use 2 bytes
name_id_compression = NETWORK_NAME_ID_COMPRESSION_16 ;
MAKE_ROOM ( ofs + 2 ) ;
encode_uint16 ( p_rpc_id , & ( packet_cache . write [ ofs ] ) ) ;
ofs + = 2 ;
}
2020-02-12 11:51:50 +01:00
2021-05-26 14:07:57 +02:00
if ( p_argcount = = 0 ) {
byte_only_or_no_args = true ;
} else if ( p_argcount = = 1 & & p_arg [ 0 ] - > get_type ( ) = = Variant : : PACKED_BYTE_ARRAY ) {
byte_only_or_no_args = true ;
// Special optimization when only the byte vector is sent.
const Vector < uint8_t > data = * p_arg [ 0 ] ;
MAKE_ROOM ( ofs + data . size ( ) ) ;
memcpy ( & ( packet_cache . write [ ofs ] ) , data . ptr ( ) , sizeof ( uint8_t ) * data . size ( ) ) ;
ofs + = data . size ( ) ;
} else {
// Arguments
MAKE_ROOM ( ofs + 1 ) ;
packet_cache . write [ ofs ] = p_argcount ;
ofs + = 1 ;
for ( int i = 0 ; i < p_argcount ; i + + ) {
int len ( 0 ) ;
2021-08-09 22:46:23 +02:00
Error err = encode_and_compress_variant ( * p_arg [ i ] , nullptr , len ) ;
2021-05-26 14:07:57 +02:00
ERR_FAIL_COND_MSG ( err ! = OK , " Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE! " ) ;
MAKE_ROOM ( ofs + len ) ;
2021-08-09 22:46:23 +02:00
encode_and_compress_variant ( * p_arg [ i ] , & ( packet_cache . write [ ofs ] ) , len ) ;
2021-05-26 14:07:57 +02:00
ofs + = len ;
2018-03-03 18:28:49 +01:00
}
}
2020-02-12 11:51:50 +01:00
ERR_FAIL_COND ( command_type > 7 ) ;
ERR_FAIL_COND ( node_id_compression > 3 ) ;
ERR_FAIL_COND ( name_id_compression > 1 ) ;
// We can now set the meta
2020-02-27 08:19:59 +01:00
packet_cache . write [ 0 ] = command_type + ( node_id_compression < < NODE_ID_COMPRESSION_SHIFT ) + ( name_id_compression < < NAME_ID_COMPRESSION_SHIFT ) + ( ( byte_only_or_no_args ? 1 : 0 ) < < BYTE_ONLY_OR_NO_ARGS_SHIFT ) ;
2020-02-12 11:51:50 +01:00
2019-09-01 18:38:58 +02:00
# ifdef DEBUG_ENABLED
2020-02-27 03:30:20 +01:00
_profile_bandwidth_data ( " out " , ofs ) ;
2019-09-01 18:38:58 +02:00
# endif
2018-09-25 17:44:26 +02:00
// Take chance and set transfer mode, since all send methods will use it.
2021-07-27 12:06:48 +02:00
network_peer - > set_transfer_channel ( p_config . channel ) ;
2021-05-26 14:07:57 +02:00
network_peer - > set_transfer_mode ( p_config . transfer_mode ) ;
2018-03-03 18:28:49 +01:00
if ( has_all_peers ) {
2018-09-25 17:44:26 +02:00
// They all have verified paths, so send fast.
network_peer - > set_target_peer ( p_to ) ; // To all of you.
network_peer - > put_packet ( packet_cache . ptr ( ) , ofs ) ; // A message with love.
2018-03-03 18:28:49 +01:00
} else {
2020-02-12 11:51:50 +01:00
// Unreachable because the node ID is never compressed if the peers doesn't know it.
CRASH_COND ( node_id_compression ! = NETWORK_NODE_ID_COMPRESSION_32 ) ;
2018-09-25 17:44:26 +02:00
// Not all verified path, so send one by one.
2018-03-03 18:28:49 +01:00
2018-09-25 17:44:26 +02:00
// Append path at the end, since we will need it for some packets.
2018-03-03 18:28:49 +01:00
CharString pname = String ( from_path ) . utf8 ( ) ;
2020-04-02 01:20:12 +02:00
int path_len = encode_cstring ( pname . get_data ( ) , nullptr ) ;
2018-03-03 18:28:49 +01:00
MAKE_ROOM ( ofs + path_len ) ;
2018-07-25 03:11:03 +02:00
encode_cstring ( pname . get_data ( ) , & ( packet_cache . write [ ofs ] ) ) ;
2018-03-03 18:28:49 +01:00
for ( Set < int > : : Element * E = connected_peers . front ( ) ; E ; E = E - > next ( ) ) {
2020-05-14 16:41:43 +02:00
if ( p_to < 0 & & E - > get ( ) = = - p_to ) {
2018-09-25 17:44:26 +02:00
continue ; // Continue, excluded.
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
2020-05-14 16:41:43 +02:00
if ( p_to > 0 & & E - > get ( ) ! = p_to ) {
2018-09-25 17:44:26 +02:00
continue ; // Continue, not for this peer.
2020-05-14 16:41:43 +02:00
}
2018-03-03 18:28:49 +01:00
Map < int , bool > : : Element * F = psc - > confirmed_peers . find ( E - > get ( ) ) ;
2018-09-25 17:44:26 +02:00
ERR_CONTINUE ( ! F ) ; // Should never happen.
2018-03-03 18:28:49 +01:00
2018-09-25 17:44:26 +02:00
network_peer - > set_target_peer ( E - > get ( ) ) ; // To this one specifically.
2018-03-03 18:28:49 +01:00
2018-10-03 19:40:37 +02:00
if ( F - > get ( ) ) {
2018-09-25 17:44:26 +02:00
// This one confirmed path, so use id.
2018-07-25 03:11:03 +02:00
encode_uint32 ( psc - > id , & ( packet_cache . write [ 1 ] ) ) ;
2018-03-03 18:28:49 +01:00
network_peer - > put_packet ( packet_cache . ptr ( ) , ofs ) ;
} else {
2018-09-25 17:44:26 +02:00
// This one did not confirm path yet, so use entire path (sorry!).
encode_uint32 ( 0x80000000 | ofs , & ( packet_cache . write [ 1 ] ) ) ; // Offset to path and flag.
2018-03-03 18:28:49 +01:00
network_peer - > put_packet ( packet_cache . ptr ( ) , ofs + path_len ) ;
}
}
}
}
2018-05-08 11:49:57 +02:00
void MultiplayerAPI : : _add_peer ( int p_id ) {
2018-03-03 18:28:49 +01:00
connected_peers . insert ( p_id ) ;
path_get_cache . insert ( p_id , PathGetCache ( ) ) ;
2021-07-30 02:14:37 +02:00
if ( is_network_server ( ) ) {
2021-08-09 22:46:23 +02:00
replicator - > spawn_all ( p_id ) ;
2021-07-30 02:14:37 +02:00
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " network_peer_connected " ) , p_id ) ;
2018-03-03 18:28:49 +01:00
}
2018-05-08 11:49:57 +02:00
void MultiplayerAPI : : _del_peer ( int p_id ) {
2018-03-03 18:28:49 +01:00
connected_peers . erase ( p_id ) ;
2019-10-25 18:00:49 +02:00
// Cleanup get cache.
path_get_cache . erase ( p_id ) ;
// Cleanup sent cache.
// Some refactoring is needed to make this faster and do paths GC.
List < NodePath > keys ;
path_send_cache . get_key_list ( & keys ) ;
2021-07-24 15:46:25 +02:00
for ( const NodePath & E : keys ) {
2021-07-16 05:45:57 +02:00
PathSentCache * psc = path_send_cache . getptr ( E ) ;
2019-10-25 18:00:49 +02:00
psc - > confirmed_peers . erase ( p_id ) ;
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " network_peer_disconnected " ) , p_id ) ;
2018-03-03 18:28:49 +01:00
}
2018-05-08 11:49:57 +02:00
void MultiplayerAPI : : _connected_to_server ( ) {
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " connected_to_server " ) ) ;
2018-03-03 18:28:49 +01:00
}
2018-05-08 11:49:57 +02:00
void MultiplayerAPI : : _connection_failed ( ) {
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " connection_failed " ) ) ;
2018-03-03 18:28:49 +01:00
}
2018-05-08 11:49:57 +02:00
void MultiplayerAPI : : _server_disconnected ( ) {
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " server_disconnected " ) ) ;
2018-03-03 18:28:49 +01:00
}
void MultiplayerAPI : : rpcp ( Node * p_node , int p_peer_id , bool p_unreliable , const StringName & p_method , const Variant * * p_arg , int p_argcount ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( ! network_peer . is_valid ( ) , " Trying to call an RPC while no network peer is active. " ) ;
ERR_FAIL_COND_MSG ( ! p_node - > is_inside_tree ( ) , " Trying to call an RPC on a node which is not inside SceneTree. " ) ;
2021-07-12 16:11:05 +02:00
ERR_FAIL_COND_MSG ( network_peer - > get_connection_status ( ) ! = MultiplayerPeer : : CONNECTION_CONNECTED , " Trying to call an RPC via a network peer which is not connected. " ) ;
2018-03-03 18:28:49 +01:00
int node_id = network_peer - > get_unique_id ( ) ;
bool call_local_native = false ;
bool call_local_script = false ;
2021-05-26 14:07:57 +02:00
uint16_t rpc_id = UINT16_MAX ;
const RPCConfig config = _get_rpc_config ( p_node , p_method , rpc_id ) ;
ERR_FAIL_COND_MSG ( config . name = = StringName ( ) ,
vformat ( " Unable to get the RPC configuration for the function \" %s \" at path: \" %s \" . This happens when the method is not marked for RPCs. " , p_method , p_node - > get_path ( ) ) ) ;
2018-03-03 18:28:49 +01:00
if ( p_peer_id = = 0 | | p_peer_id = = node_id | | ( p_peer_id < 0 & & p_peer_id ! = - node_id ) ) {
2021-05-26 14:07:57 +02:00
if ( rpc_id & ( 1 < < 15 ) ) {
2021-06-24 10:28:15 +02:00
call_local_native = config . sync ;
2021-05-26 14:07:57 +02:00
} else {
2021-06-24 10:28:15 +02:00
call_local_script = config . sync ;
2018-03-03 18:28:49 +01:00
}
}
2021-06-24 10:28:15 +02:00
if ( p_peer_id ! = node_id ) {
2019-09-01 18:38:58 +02:00
# ifdef DEBUG_ENABLED
2020-02-27 03:30:20 +01:00
_profile_node_data ( " out_rpc " , p_node - > get_instance_id ( ) ) ;
2019-09-01 18:38:58 +02:00
# endif
2021-05-26 14:07:57 +02:00
_send_rpc ( p_node , p_peer_id , rpc_id , config , p_method , p_arg , p_argcount ) ;
2018-03-03 18:28:49 +01:00
}
if ( call_local_native ) {
2019-01-12 19:31:19 +01:00
int temp_id = rpc_sender_id ;
rpc_sender_id = get_network_unique_id ( ) ;
2020-02-19 20:27:19 +01:00
Callable : : CallError ce ;
2018-03-03 18:28:49 +01:00
p_node - > call ( p_method , p_arg , p_argcount , ce ) ;
2019-01-12 19:31:19 +01:00
rpc_sender_id = temp_id ;
2020-02-19 20:27:19 +01:00
if ( ce . error ! = Callable : : CallError : : CALL_OK ) {
2018-03-03 18:28:49 +01:00
String error = Variant : : get_call_error_text ( p_node , p_method , p_arg , p_argcount , ce ) ;
2019-08-15 04:57:49 +02:00
error = " rpc() aborted in local call: - " + error + " . " ;
2019-11-06 17:03:04 +01:00
ERR_PRINT ( error ) ;
2018-03-03 18:28:49 +01:00
return ;
}
}
if ( call_local_script ) {
2019-01-12 19:31:19 +01:00
int temp_id = rpc_sender_id ;
rpc_sender_id = get_network_unique_id ( ) ;
2020-02-19 20:27:19 +01:00
Callable : : CallError ce ;
ce . error = Callable : : CallError : : CALL_OK ;
2018-03-03 18:28:49 +01:00
p_node - > get_script_instance ( ) - > call ( p_method , p_arg , p_argcount , ce ) ;
2019-01-12 19:31:19 +01:00
rpc_sender_id = temp_id ;
2020-02-19 20:27:19 +01:00
if ( ce . error ! = Callable : : CallError : : CALL_OK ) {
2018-03-03 18:28:49 +01:00
String error = Variant : : get_call_error_text ( p_node , p_method , p_arg , p_argcount , ce ) ;
2019-08-15 04:57:49 +02:00
error = " rpc() aborted in script local call: - " + error + " . " ;
2019-11-06 17:03:04 +01:00
ERR_PRINT ( error ) ;
2018-03-03 18:28:49 +01:00
return ;
}
}
2019-05-08 22:10:38 +02:00
2021-06-24 10:28:15 +02:00
ERR_FAIL_COND_MSG ( p_peer_id = = node_id & & ! config . sync , " RPC ' " + p_method + " ' on yourself is not allowed by selected mode. " ) ;
2018-03-03 18:28:49 +01:00
}
2021-07-27 12:06:48 +02:00
Error MultiplayerAPI : : send_bytes ( Vector < uint8_t > p_data , int p_to , MultiplayerPeer : : TransferMode p_mode , int p_channel ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( p_data . size ( ) < 1 , ERR_INVALID_DATA , " Trying to send an empty raw packet. " ) ;
ERR_FAIL_COND_V_MSG ( ! network_peer . is_valid ( ) , ERR_UNCONFIGURED , " Trying to send a raw packet while no network peer is active. " ) ;
2021-07-12 16:11:05 +02:00
ERR_FAIL_COND_V_MSG ( network_peer - > get_connection_status ( ) ! = MultiplayerPeer : : CONNECTION_CONNECTED , ERR_UNCONFIGURED , " Trying to send a raw packet via a network peer which is not connected. " ) ;
2018-05-11 21:34:45 +02:00
MAKE_ROOM ( p_data . size ( ) + 1 ) ;
2020-02-17 22:06:54 +01:00
const uint8_t * r = p_data . ptr ( ) ;
2018-07-25 03:11:03 +02:00
packet_cache . write [ 0 ] = NETWORK_COMMAND_RAW ;
memcpy ( & packet_cache . write [ 1 ] , & r [ 0 ] , p_data . size ( ) ) ;
2018-07-08 09:47:22 +02:00
2018-05-11 21:34:45 +02:00
network_peer - > set_target_peer ( p_to ) ;
2021-07-27 12:06:48 +02:00
network_peer - > set_transfer_channel ( p_channel ) ;
2018-07-08 09:47:22 +02:00
network_peer - > set_transfer_mode ( p_mode ) ;
2018-05-11 21:34:45 +02:00
return network_peer - > put_packet ( packet_cache . ptr ( ) , p_data . size ( ) + 1 ) ;
}
void MultiplayerAPI : : _process_raw ( int p_from , const uint8_t * p_packet , int p_packet_len ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( p_packet_len < 2 , " Invalid packet received. Size too small. " ) ;
2018-05-11 21:34:45 +02:00
2020-02-17 22:06:54 +01:00
Vector < uint8_t > out ;
2018-05-11 21:34:45 +02:00
int len = p_packet_len - 1 ;
out . resize ( len ) ;
{
2020-02-17 22:06:54 +01:00
uint8_t * w = out . ptrw ( ) ;
2018-05-11 21:34:45 +02:00
memcpy ( & w [ 0 ] , & p_packet [ 1 ] , len ) ;
}
2021-07-17 23:22:52 +02:00
emit_signal ( SNAME ( " network_peer_packet " ) , p_from , out ) ;
2018-05-11 21:34:45 +02:00
}
2021-08-09 22:46:23 +02:00
bool MultiplayerAPI : : send_confirm_path ( Node * p_node , NodePath p_path , int p_peer_id , int & r_id ) {
// See if the path is cached.
PathSentCache * psc = path_send_cache . getptr ( p_path ) ;
if ( ! psc ) {
// Path is not cached, create.
path_send_cache [ p_path ] = PathSentCache ( ) ;
psc = path_send_cache . getptr ( p_path ) ;
psc - > id = last_send_cache_id + + ;
}
r_id = psc - > id ;
// See if all peers have cached path (if so, call can be fast).
return _send_confirm_path ( p_node , p_path , psc , p_peer_id ) ;
}
Node * MultiplayerAPI : : get_cached_node ( int p_from , uint32_t p_node_id ) {
Map < int , PathGetCache > : : Element * E = path_get_cache . find ( p_from ) ;
ERR_FAIL_COND_V_MSG ( ! E , nullptr , vformat ( " No cache found for peer %d. " , p_from ) ) ;
Map < int , PathGetCache : : NodeInfo > : : Element * F = E - > get ( ) . nodes . find ( p_node_id ) ;
ERR_FAIL_COND_V_MSG ( ! F , nullptr , vformat ( " ID %d not found in cache of peer %d. " , p_node_id , p_from ) ) ;
PathGetCache : : NodeInfo * ni = & F - > get ( ) ;
Node * node = root_node - > get_node ( ni - > path ) ;
if ( ! node ) {
ERR_PRINT ( " Failed to get cached path: " + String ( ni - > path ) + " . " ) ;
}
return node ;
}
2018-03-03 18:28:49 +01:00
int MultiplayerAPI : : get_network_unique_id ( ) const {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( ! network_peer . is_valid ( ) , 0 , " No network peer is assigned. Unable to get unique network ID. " ) ;
2018-03-03 18:28:49 +01:00
return network_peer - > get_unique_id ( ) ;
}
bool MultiplayerAPI : : is_network_server ( ) const {
2021-03-01 14:25:07 +01:00
return network_peer . is_valid ( ) & & network_peer - > is_server ( ) ;
2018-03-03 18:28:49 +01:00
}
void MultiplayerAPI : : set_refuse_new_network_connections ( bool p_refuse ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( ! network_peer . is_valid ( ) , " No network peer is assigned. Unable to set 'refuse_new_connections'. " ) ;
2018-03-03 18:28:49 +01:00
network_peer - > set_refuse_new_connections ( p_refuse ) ;
}
bool MultiplayerAPI : : is_refusing_new_network_connections ( ) const {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( ! network_peer . is_valid ( ) , false , " No network peer is assigned. Unable to get 'refuse_new_connections'. " ) ;
2018-03-03 18:28:49 +01:00
return network_peer - > is_refusing_new_connections ( ) ;
}
Vector < int > MultiplayerAPI : : get_network_connected_peers ( ) const {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( ! network_peer . is_valid ( ) , Vector < int > ( ) , " No network peer is assigned. Assume no peers are connected. " ) ;
2018-03-03 18:28:49 +01:00
Vector < int > ret ;
for ( Set < int > : : Element * E = connected_peers . front ( ) ; E ; E = E - > next ( ) ) {
ret . push_back ( E - > get ( ) ) ;
}
return ret ;
}
2019-03-26 16:52:42 +01:00
void MultiplayerAPI : : set_allow_object_decoding ( bool p_enable ) {
allow_object_decoding = p_enable ;
}
bool MultiplayerAPI : : is_object_decoding_allowed ( ) const {
return allow_object_decoding ;
}
2021-08-09 22:46:23 +02:00
MultiplayerReplicator * MultiplayerAPI : : get_replicator ( ) const {
return replicator ;
2021-07-30 02:14:37 +02:00
}
2021-08-09 22:46:23 +02:00
void MultiplayerAPI : : scene_enter_exit_notify ( const String & p_scene , Node * p_node , bool p_enter ) {
replicator - > scene_enter_exit_notify ( p_scene , p_node , p_enter ) ;
2021-07-30 02:14:37 +02:00
}
2018-03-03 18:28:49 +01:00
void MultiplayerAPI : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " set_root_node " , " node " ) , & MultiplayerAPI : : set_root_node ) ;
2020-06-01 10:34:15 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_root_node " ) , & MultiplayerAPI : : get_root_node ) ;
2021-07-27 12:06:48 +02:00
ClassDB : : bind_method ( D_METHOD ( " send_bytes " , " bytes " , " id " , " mode " , " channel " ) , & MultiplayerAPI : : send_bytes , DEFVAL ( MultiplayerPeer : : TARGET_PEER_BROADCAST ) , DEFVAL ( MultiplayerPeer : : TRANSFER_MODE_RELIABLE ) , DEFVAL ( 0 ) ) ;
2018-03-03 18:28:49 +01:00
ClassDB : : bind_method ( D_METHOD ( " has_network_peer " ) , & MultiplayerAPI : : has_network_peer ) ;
ClassDB : : bind_method ( D_METHOD ( " get_network_peer " ) , & MultiplayerAPI : : get_network_peer ) ;
ClassDB : : bind_method ( D_METHOD ( " get_network_unique_id " ) , & MultiplayerAPI : : get_network_unique_id ) ;
ClassDB : : bind_method ( D_METHOD ( " is_network_server " ) , & MultiplayerAPI : : is_network_server ) ;
ClassDB : : bind_method ( D_METHOD ( " get_rpc_sender_id " ) , & MultiplayerAPI : : get_rpc_sender_id ) ;
ClassDB : : bind_method ( D_METHOD ( " set_network_peer " , " peer " ) , & MultiplayerAPI : : set_network_peer ) ;
ClassDB : : bind_method ( D_METHOD ( " poll " ) , & MultiplayerAPI : : poll ) ;
ClassDB : : bind_method ( D_METHOD ( " clear " ) , & MultiplayerAPI : : clear ) ;
ClassDB : : bind_method ( D_METHOD ( " get_network_connected_peers " ) , & MultiplayerAPI : : get_network_connected_peers ) ;
ClassDB : : bind_method ( D_METHOD ( " set_refuse_new_network_connections " , " refuse " ) , & MultiplayerAPI : : set_refuse_new_network_connections ) ;
ClassDB : : bind_method ( D_METHOD ( " is_refusing_new_network_connections " ) , & MultiplayerAPI : : is_refusing_new_network_connections ) ;
2019-03-26 16:52:42 +01:00
ClassDB : : bind_method ( D_METHOD ( " set_allow_object_decoding " , " enable " ) , & MultiplayerAPI : : set_allow_object_decoding ) ;
ClassDB : : bind_method ( D_METHOD ( " is_object_decoding_allowed " ) , & MultiplayerAPI : : is_object_decoding_allowed ) ;
2021-08-09 22:46:23 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_replicator " ) , & MultiplayerAPI : : get_replicator ) ;
2019-03-26 16:52:42 +01:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " allow_object_decoding " ) , " set_allow_object_decoding " , " is_object_decoding_allowed " ) ;
2018-03-03 18:28:49 +01:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " refuse_new_network_connections " ) , " set_refuse_new_network_connections " , " is_refusing_new_network_connections " ) ;
2021-07-12 16:11:05 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " network_peer " , PROPERTY_HINT_RESOURCE_TYPE , " MultiplayerPeer " , PROPERTY_USAGE_NONE ) , " set_network_peer " , " get_network_peer " ) ;
2021-06-18 01:10:18 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " root_node " , PROPERTY_HINT_RESOURCE_TYPE , " Node " , PROPERTY_USAGE_NONE ) , " set_root_node " , " get_root_node " ) ;
2019-06-01 15:42:22 +02:00
ADD_PROPERTY_DEFAULT ( " refuse_new_network_connections " , false ) ;
2021-08-09 22:46:23 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : OBJECT , " replicator " , PROPERTY_HINT_RESOURCE_TYPE , " MultiplayerReplicator " , PROPERTY_USAGE_NONE ) , " " , " get_replicator " ) ;
2018-03-03 18:28:49 +01:00
ADD_SIGNAL ( MethodInfo ( " network_peer_connected " , PropertyInfo ( Variant : : INT , " id " ) ) ) ;
ADD_SIGNAL ( MethodInfo ( " network_peer_disconnected " , PropertyInfo ( Variant : : INT , " id " ) ) ) ;
2020-02-17 22:06:54 +01:00
ADD_SIGNAL ( MethodInfo ( " network_peer_packet " , PropertyInfo ( Variant : : INT , " id " ) , PropertyInfo ( Variant : : PACKED_BYTE_ARRAY , " packet " ) ) ) ;
2018-03-03 18:28:49 +01:00
ADD_SIGNAL ( MethodInfo ( " connected_to_server " ) ) ;
ADD_SIGNAL ( MethodInfo ( " connection_failed " ) ) ;
ADD_SIGNAL ( MethodInfo ( " server_disconnected " ) ) ;
2018-05-13 07:07:56 +02:00
BIND_ENUM_CONSTANT ( RPC_MODE_DISABLED ) ;
BIND_ENUM_CONSTANT ( RPC_MODE_REMOTE ) ;
BIND_ENUM_CONSTANT ( RPC_MODE_MASTER ) ;
2018-09-14 21:59:47 +02:00
BIND_ENUM_CONSTANT ( RPC_MODE_PUPPET ) ;
2018-03-03 18:28:49 +01:00
}
2020-05-12 17:01:17 +02:00
MultiplayerAPI : : MultiplayerAPI ( ) {
2021-08-09 22:46:23 +02:00
replicator = memnew ( MultiplayerReplicator ( this ) ) ;
2018-03-03 18:28:49 +01:00
clear ( ) ;
}
MultiplayerAPI : : ~ MultiplayerAPI ( ) {
clear ( ) ;
2021-08-09 22:46:23 +02:00
memdelete ( replicator ) ;
2018-03-03 18:28:49 +01:00
}