Browse Source

Optmized data sent during RPC and RSet calls.
- Now is sent the method ID rather the full function name.
- The passed IDs (Node and Method) are compressed so to use less possible space.
- The variant (INT and BOOL) is now encoded and compressed so to use much less data.
- Optimized RPCMode retrieval for GDScript functions.
- Added checksum to assert the methods are the same across peers.

This work has been kindly sponsored by IMVU.

Andrea Catania 5 năm trước cách đây
mục cha
commit
eb07e87981

+ 403 - 93
core/io/multiplayer_api.cpp

@@ -32,6 +32,7 @@
 
 #include "core/io/marshalls.h"
 #include "scene/main/node.h"
+#include <stdint.h>
 
 #ifdef DEBUG_ENABLED
 #include "core/os/os.h"
@@ -180,7 +181,8 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
 	}
 #endif
 
-	uint8_t packet_type = p_packet[0];
+	// Extract the `packet_type` from the LSB three bits:
+	uint8_t packet_type = p_packet[0] & 7;
 
 	switch (packet_type) {
 
@@ -197,31 +199,80 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
 		case NETWORK_COMMAND_REMOTE_CALL:
 		case NETWORK_COMMAND_REMOTE_SET: {
 
-			ERR_FAIL_COND_MSG(p_packet_len < 6, "Invalid packet received. Size too small.");
-
-			Node *node = _process_get_node(p_from, p_packet, p_packet_len);
+			// 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.
+			int node_id_compression = (p_packet[0] & 24) >> 3;
+			int name_id_compression = (p_packet[0] & 32) >> 5;
+
+			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.");
 
+			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();
+			}
+			Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len);
 			ERR_FAIL_COND_MSG(node == NULL, "Invalid packet received. Requested node was not found.");
 
-			// Detect cstring end.
-			int len_end = 5;
-			for (; len_end < p_packet_len; len_end++) {
-				if (p_packet[len_end] == 0) {
+			uint16_t name_id = 0;
+			switch (name_id_compression) {
+				case NETWORK_NAME_ID_COMPRESSION_8:
+					name_id = p_packet[name_id_offset];
 					break;
-				}
+				case NETWORK_NAME_ID_COMPRESSION_16:
+					name_id = decode_uint16(p_packet + name_id_offset);
+					break;
+				default:
+					// Unreachable, checked before.
+					CRASH_NOW();
 			}
 
-			ERR_FAIL_COND_MSG(len_end >= p_packet_len, "Invalid packet received. Size too small.");
-
-			StringName name = String::utf8((const char *)&p_packet[5]);
-
 			if (packet_type == NETWORK_COMMAND_REMOTE_CALL) {
 
-				_process_rpc(node, name, p_from, p_packet, p_packet_len, len_end + 1);
+				_process_rpc(node, name_id, p_from, p_packet, p_packet_len, packet_min_size);
 
 			} else {
 
-				_process_rset(node, name, p_from, p_packet, p_packet_len, len_end + 1);
+				_process_rset(node, name_id, p_from, p_packet, p_packet_len, packet_min_size);
 			}
 
 		} break;
@@ -233,15 +284,14 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
 	}
 }
 
-Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, int p_packet_len) {
+Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) {
 
-	uint32_t target = decode_uint32(&p_packet[1]);
 	Node *node = NULL;
 
-	if (target & 0x80000000) {
+	if (p_node_target & 0x80000000) {
 		// Use full path (not cached yet).
 
-		int ofs = target & 0x7FFFFFFF;
+		int ofs = p_node_target & 0x7FFFFFFF;
 
 		ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, NULL, "Invalid packet received. Size smaller than declared.");
 
@@ -256,7 +306,7 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, int
 			ERR_PRINT("Failed to get path from RPC: " + String(np) + ".");
 	} else {
 		// Use cached path.
-		int id = target;
+		int id = p_node_target;
 
 		Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from);
 		ERR_FAIL_COND_V_MSG(!E, NULL, "Invalid packet received. Requests invalid peer cache.");
@@ -274,21 +324,21 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, int
 	return node;
 }
 
-void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) {
+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) {
 
 	ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
 
 	// Check that remote can call the RPC on this node.
-	RPCMode rpc_mode = RPC_MODE_DISABLED;
-	const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_name);
-	if (E) {
-		rpc_mode = E->get();
-	} else if (p_node->get_script_instance()) {
-		rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_name);
+	StringName name = p_node->get_node_rpc_method(p_rpc_method_id);
+	RPCMode rpc_mode = p_node->get_node_rpc_mode_by_id(p_rpc_method_id);
+	if (name == StringName() && p_node->get_script_instance()) {
+		name = p_node->get_script_instance()->get_rpc_method(p_rpc_method_id);
+		rpc_mode = p_node->get_script_instance()->get_rpc_mode_by_id(p_rpc_method_id);
 	}
+	ERR_FAIL_COND(name == StringName());
 
 	bool can_call = _can_call_mode(p_node, rpc_mode, p_from);
-	ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(p_name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rpc_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
+	ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rpc_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
 
 	int argc = p_packet[p_offset];
 	Vector<Variant> args;
@@ -311,7 +361,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_
 		ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
 
 		int vlen;
-		Error err = decode_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen, allow_object_decoding || network_peer->is_object_decoding_allowed());
+		Error err = _decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen);
 		ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument.");
 
 		argp.write[i] = &args[i];
@@ -320,29 +370,29 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_
 
 	Variant::CallError ce;
 
-	p_node->call(p_name, (const Variant **)argp.ptr(), argc, ce);
+	p_node->call(name, (const Variant **)argp.ptr(), argc, ce);
 	if (ce.error != Variant::CallError::CALL_OK) {
-		String error = Variant::get_call_error_text(p_node, p_name, (const Variant **)argp.ptr(), argc, ce);
+		String error = Variant::get_call_error_text(p_node, name, (const Variant **)argp.ptr(), argc, ce);
 		error = "RPC - " + error;
 		ERR_PRINT(error);
 	}
 }
 
-void MultiplayerAPI::_process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) {
+void MultiplayerAPI::_process_rset(Node *p_node, const uint16_t p_rpc_property_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) {
 
 	ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
 
 	// Check that remote can call the RSET on this node.
-	RPCMode rset_mode = RPC_MODE_DISABLED;
-	const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_name);
-	if (E) {
-		rset_mode = E->get();
-	} else if (p_node->get_script_instance()) {
-		rset_mode = p_node->get_script_instance()->get_rset_mode(p_name);
+	StringName name = p_node->get_node_rset_property(p_rpc_property_id);
+	RPCMode rset_mode = p_node->get_node_rset_mode_by_id(p_rpc_property_id);
+	if (name == StringName() && p_node->get_script_instance()) {
+		name = p_node->get_script_instance()->get_rset_property(p_rpc_property_id);
+		rset_mode = p_node->get_script_instance()->get_rset_mode_by_id(p_rpc_property_id);
 	}
+	ERR_FAIL_COND(name == StringName());
 
 	bool can_call = _can_call_mode(p_node, rset_mode, p_from);
-	ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(p_name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
+	ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
 
 #ifdef DEBUG_ENABLED
 	if (profiling) {
@@ -353,26 +403,33 @@ void MultiplayerAPI::_process_rset(Node *p_node, const StringName &p_name, int p
 #endif
 
 	Variant value;
-	Error err = decode_variant(value, &p_packet[p_offset], p_packet_len - p_offset, NULL, allow_object_decoding || network_peer->is_object_decoding_allowed());
+	Error err = _decode_and_decompress_variant(value, &p_packet[p_offset], p_packet_len - p_offset, NULL);
 
 	ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RSET value.");
 
 	bool valid;
 
-	p_node->set(p_name, value, &valid);
+	p_node->set(name, value, &valid);
 	if (!valid) {
-		String error = "Error setting remote property '" + String(p_name) + "', not found in object of type " + p_node->get_class() + ".";
+		String error = "Error setting remote property '" + String(name) + "', not found in object of type " + p_node->get_class() + ".";
 		ERR_PRINT(error);
 	}
 }
 
 void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
 
-	ERR_FAIL_COND_MSG(p_packet_len < 5, "Invalid packet received. Size too small.");
-	int id = decode_uint32(&p_packet[1]);
+	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;
 
 	String paths;
-	paths.parse_utf8((const char *)&p_packet[5], p_packet_len - 5);
+	paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs);
 
 	NodePath path = paths;
 
@@ -380,6 +437,13 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet,
 		path_get_cache[p_from] = PathGetCache();
 	}
 
+	Node *node = root_node->get_node(path);
+	ERR_FAIL_COND(node == NULL);
+	const bool valid_rpc_checksum = node->get_rpc_md5() == methods_md5;
+	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);
+	}
+
 	PathGetCache::NodeInfo ni;
 	ni.path = path;
 	ni.instance = 0;
@@ -392,9 +456,10 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet,
 
 	Vector<uint8_t> packet;
 
-	packet.resize(1 + len);
+	packet.resize(1 + 1 + len);
 	packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH;
-	encode_cstring(pname.get_data(), &packet.write[1]);
+	packet.write[1] = valid_rpc_checksum;
+	encode_cstring(pname.get_data(), &packet.write[2]);
 
 	network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE);
 	network_peer->set_target_peer(p_from);
@@ -403,13 +468,19 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet,
 
 void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
 
-	ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small.");
+	ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small.");
+
+	const bool valid_rpc_checksum = p_packet[1];
 
 	String paths;
-	paths.parse_utf8((const char *)&p_packet[1], p_packet_len - 1);
+	paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2);
 
 	NodePath path = paths;
 
+	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);
+	}
+
 	PathSentCache *psc = path_send_cache.getptr(path);
 	ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache.");
 
@@ -418,7 +489,7 @@ void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet,
 	E->get() = true;
 }
 
-bool MultiplayerAPI::_send_confirm_path(NodePath p_path, PathSentCache *psc, int p_target) {
+bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target) {
 	bool has_all_peers = true;
 	List<int> peers_to_add; // If one is missing, take note to add it.
 
@@ -443,31 +514,192 @@ bool MultiplayerAPI::_send_confirm_path(NodePath p_path, PathSentCache *psc, int
 		}
 	}
 
-	// Those that need to be added, send a message for this.
+	if (peers_to_add.size() > 0) {
 
-	for (List<int>::Element *E = peers_to_add.front(); E; E = E->next()) {
+		// Those that need to be added, send a message for this.
 
 		// Encode function name.
-		CharString pname = String(p_path).utf8();
-		int len = encode_cstring(pname.get_data(), NULL);
+		const CharString path = String(p_path).utf8();
+		const int path_len = encode_cstring(path.get_data(), NULL);
+
+		// Extract MD5 from rpc methods list.
+		const String methods_md5 = p_node->get_rpc_md5();
+		const int methods_md5_len = 33; // 32 + 1 for the `0` that is added by the encoder.
 
 		Vector<uint8_t> packet;
+		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]);
 
-		packet.resize(1 + 4 + len);
-		packet.write[0] = NETWORK_COMMAND_SIMPLIFY_PATH;
-		encode_uint32(psc->id, &packet.write[1]);
-		encode_cstring(pname.get_data(), &packet.write[5]);
+		for (List<int>::Element *E = peers_to_add.front(); E; E = E->next()) {
 
-		network_peer->set_target_peer(E->get()); // To all of you.
-		network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE);
-		network_peer->put_packet(packet.ptr(), packet.size());
+			network_peer->set_target_peer(E->get()); // To all of you.
+			network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE);
+			network_peer->put_packet(packet.ptr(), packet.size());
 
-		psc->confirmed_peers.insert(E->get(), false); // Insert into confirmed, but as false since it was not confirmed.
+			psc->confirmed_peers.insert(E->get(), false); // Insert into confirmed, but as false since it was not confirmed.
+		}
 	}
 
 	return has_all_peers;
 }
 
+// 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
+Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) {
+
+	// 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.
+			Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+			if (err != OK)
+				return err;
+			if (r_buffer) {
+				// The first byte is not used by the marshaling, so store the type
+				// so we know how to decompress and decode this variant.
+				r_buffer[0] = p_variant.get_type();
+			}
+	}
+
+	return OK;
+}
+Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) {
+
+	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;
+			if (r_len)
+				*r_len = 1;
+		} break;
+		case Variant::INT: {
+			buf += 1;
+			len -= 1;
+			if (r_len)
+				*r_len = 1;
+			if (encode_mode == ENCODE_8) {
+				// 8 bits.
+				ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
+				int8_t val = buf[0];
+				r_variant = val;
+				if (r_len)
+					(*r_len) += 1;
+			} 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;
+				if (r_len)
+					(*r_len) += 2;
+			} 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;
+				if (r_len)
+					(*r_len) += 4;
+			} else {
+				// 64 bits.
+				ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
+				int64_t val = decode_uint64(buf);
+				r_variant = val;
+				if (r_len)
+					(*r_len) += 8;
+			}
+		} break;
+		default:
+			Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+			if (err != OK)
+				return err;
+	}
+
+	return OK;
+}
+
 void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount) {
 
 	ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree.");
@@ -496,6 +728,9 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
 		psc->id = last_send_cache_id++;
 	}
 
+	// 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);
+
 	// Create base packet, lots of hardcode because it must be tight.
 
 	int ofs = 0;
@@ -503,45 +738,125 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
 #define MAKE_ROOM(m_amount) \
 	if (packet_cache.size() < m_amount) packet_cache.resize(m_amount);
 
-	// Encode type.
+	// Encode meta.
+	// The meta is composed by a single byte that contains (starting from the least segnificant bit):
+	// - `NetworkCommands` in the first three bits.
+	// - `NetworkNodeIdCompression` in the next 2 bits.
+	// - `NetworkNameIdCompression` in the next 1 bit.
+	// - So we still have the last two bits free!
+	uint8_t command_type = p_set ? NETWORK_COMMAND_REMOTE_SET : NETWORK_COMMAND_REMOTE_CALL;
+	uint8_t node_id_compression = UINT8_MAX;
+	uint8_t name_id_compression = UINT8_MAX;
+
 	MAKE_ROOM(1);
-	packet_cache.write[0] = p_set ? NETWORK_COMMAND_REMOTE_SET : NETWORK_COMMAND_REMOTE_CALL;
+	// The meta is composed along the way, so just set 0 for now.
+	packet_cache.write[0] = 0;
 	ofs += 1;
 
-	// Encode ID.
-	MAKE_ROOM(ofs + 4);
-	encode_uint32(psc->id, &(packet_cache.write[ofs]));
-	ofs += 4;
-
-	// Encode function name.
-	CharString name = String(p_name).utf8();
-	int len = encode_cstring(name.get_data(), NULL);
-	MAKE_ROOM(ofs + len);
-	encode_cstring(name.get_data(), &(packet_cache.write[ofs]));
-	ofs += len;
+	// 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 {
+		// The targets doesn't know the node yet, so we need to use 32 bits int.
+		node_id_compression = NETWORK_NODE_ID_COMPRESSION_32;
+		MAKE_ROOM(ofs + 4);
+		encode_uint32(psc->id, &(packet_cache.write[ofs]));
+		ofs += 4;
+	}
 
 	if (p_set) {
+
+		// Take the rpc property ID
+		uint16_t property_id = p_from->get_node_rset_property_id(p_name);
+		if (property_id == UINT16_MAX && p_from->get_script_instance()) {
+			property_id = p_from->get_script_instance()->get_rset_property_id(p_name);
+		}
+		ERR_FAIL_COND_MSG(property_id == UINT16_MAX, "Unable to take the `property_id` for the property:" + p_name + ". this can happen only if this property is not marked as `remote`.");
+
+		if (property_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>(property_id);
+			ofs += 1;
+		} else {
+			// The ID is larger, let's use 2 bytes
+			name_id_compression = NETWORK_NAME_ID_COMPRESSION_16;
+			MAKE_ROOM(ofs + 2);
+			encode_uint16(property_id, &(packet_cache.write[ofs]));
+			ofs += 2;
+		}
+
 		// Set argument.
-		Error err = encode_variant(*p_arg[0], NULL, len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+		int len(0);
+		Error err = _encode_and_compress_variant(*p_arg[0], NULL, len);
 		ERR_FAIL_COND_MSG(err != OK, "Unable to encode RSET value. THIS IS LIKELY A BUG IN THE ENGINE!");
 		MAKE_ROOM(ofs + len);
-		encode_variant(*p_arg[0], &(packet_cache.write[ofs]), len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+		_encode_and_compress_variant(*p_arg[0], &(packet_cache.write[ofs]), len);
 		ofs += len;
 
 	} else {
+		// Take the rpc method ID
+		uint16_t method_id = p_from->get_node_rpc_method_id(p_name);
+		if (method_id == UINT16_MAX && p_from->get_script_instance()) {
+			method_id = p_from->get_script_instance()->get_rpc_method_id(p_name);
+		}
+		ERR_FAIL_COND_MSG(method_id == UINT16_MAX, "Unable to take the `method_id` for the function:" + p_name + ". this can happen only if this method is not marked as `remote`.");
+
+		if (method_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>(method_id);
+			ofs += 1;
+		} else {
+			// The ID is larger, let's use 2 bytes
+			name_id_compression = NETWORK_NAME_ID_COMPRESSION_16;
+			MAKE_ROOM(ofs + 2);
+			encode_uint16(method_id, &(packet_cache.write[ofs]));
+			ofs += 2;
+		}
+
 		// Call arguments.
 		MAKE_ROOM(ofs + 1);
 		packet_cache.write[ofs] = p_argcount;
 		ofs += 1;
 		for (int i = 0; i < p_argcount; i++) {
-			Error err = encode_variant(*p_arg[i], NULL, len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+			int len(0);
+			Error err = _encode_and_compress_variant(*p_arg[i], NULL, len);
 			ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!");
 			MAKE_ROOM(ofs + len);
-			encode_variant(*p_arg[i], &(packet_cache.write[ofs]), len, allow_object_decoding || network_peer->is_object_decoding_allowed());
+			_encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len);
 			ofs += len;
 		}
 	}
 
+	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
+	packet_cache.write[0] = command_type + (node_id_compression << 3) + (name_id_compression << 5);
+
 #ifdef DEBUG_ENABLED
 	if (profiling) {
 		bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].timestamp = OS::get_singleton()->get_ticks_msec();
@@ -550,9 +865,6 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
 	}
 #endif
 
-	// See if all peers have cached path (is so, call can be fast).
-	bool has_all_peers = _send_confirm_path(from_path, psc, p_to);
-
 	// Take chance and set transfer mode, since all send methods will use it.
 	network_peer->set_transfer_mode(p_unreliable ? NetworkedMultiplayerPeer::TRANSFER_MODE_UNRELIABLE : NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE);
 
@@ -562,6 +874,9 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
 		network_peer->set_target_peer(p_to); // To all of you.
 		network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love.
 	} else {
+		// 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);
+
 		// Not all verified path, so send one by one.
 
 		// Append path at the end, since we will need it for some packets.
@@ -647,16 +962,14 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const
 	if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) {
 		// Check that send mode can use local call.
 
-		const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_method);
-		if (E) {
-			call_local_native = _should_call_local(E->get(), is_master, skip_rpc);
-		}
+		RPCMode rpc_mode = p_node->get_node_rpc_mode(p_method);
+		call_local_native = _should_call_local(rpc_mode, is_master, skip_rpc);
 
 		if (call_local_native) {
 			// Done below.
 		} else if (p_node->get_script_instance()) {
 			// Attempt with script.
-			RPCMode rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method);
+			rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method);
 			call_local_script = _should_call_local(rpc_mode, is_master, skip_rpc);
 		}
 	}
@@ -719,11 +1032,8 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const
 
 	if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) {
 		// Check that send mode can use local call.
-		const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_property);
-		if (E) {
-
-			set_local = _should_call_local(E->get(), is_master, skip_rset);
-		}
+		RPCMode rpc_mode = p_node->get_node_rset_mode(p_property);
+		set_local = _should_call_local(rpc_mode, is_master, skip_rset);
 
 		if (set_local) {
 			bool valid;
@@ -740,7 +1050,7 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const
 			}
 		} else if (p_node->get_script_instance()) {
 			// Attempt with script.
-			RPCMode rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property);
+			rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property);
 
 			set_local = _should_call_local(rpc_mode, is_master, skip_rset);
 

+ 19 - 5
core/io/multiplayer_api.h

@@ -98,23 +98,37 @@ protected:
 	void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len);
 	void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len);
 	void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len);
-	Node *_process_get_node(int p_from, const uint8_t *p_packet, int p_packet_len);
-	void _process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
-	void _process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
+	Node *_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len);
+	void _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);
+	void _process_rset(Node *p_node, const uint16_t p_rpc_property_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
 	void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
 
 	void _send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount);
-	bool _send_confirm_path(NodePath p_path, PathSentCache *psc, int p_target);
+	bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target);
+
+	Error _encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len);
+	Error _decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len);
 
 public:
 	enum NetworkCommands {
-		NETWORK_COMMAND_REMOTE_CALL,
+		NETWORK_COMMAND_REMOTE_CALL = 0,
 		NETWORK_COMMAND_REMOTE_SET,
 		NETWORK_COMMAND_SIMPLIFY_PATH,
 		NETWORK_COMMAND_CONFIRM_PATH,
 		NETWORK_COMMAND_RAW,
 	};
 
+	enum NetworkNodeIdCompression {
+		NETWORK_NODE_ID_COMPRESSION_8 = 0,
+		NETWORK_NODE_ID_COMPRESSION_16,
+		NETWORK_NODE_ID_COMPRESSION_32,
+	};
+
+	enum NetworkNameIdCompression {
+		NETWORK_NAME_ID_COMPRESSION_8 = 0,
+		NETWORK_NAME_ID_COMPRESSION_16,
+	};
+
 	enum RPCMode {
 
 		RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)

+ 9 - 0
core/script_language.cpp

@@ -32,6 +32,7 @@
 
 #include "core/core_string_names.h"
 #include "core/project_settings.h"
+#include <stdint.h>
 
 ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
 int ScriptServer::_language_count = 0;
@@ -644,6 +645,14 @@ Variant PlaceHolderScriptInstance::property_get_fallback(const StringName &p_nam
 	return Variant();
 }
 
+uint16_t PlaceHolderScriptInstance::get_rpc_method_id(const StringName &p_method) const {
+	return UINT16_MAX;
+}
+
+uint16_t PlaceHolderScriptInstance::get_rset_property_id(const StringName &p_method) const {
+	return UINT16_MAX;
+}
+
 PlaceHolderScriptInstance::PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner) :
 		owner(p_owner),
 		language(p_language),

+ 45 - 0
core/script_language.h

@@ -40,6 +40,21 @@ class ScriptLanguage;
 
 typedef void (*ScriptEditRequestFunction)(const String &p_path);
 
+struct ScriptNetData {
+	StringName name;
+	MultiplayerAPI::RPCMode mode;
+	bool operator==(ScriptNetData const &p_other) const {
+		return name == p_other.name;
+	}
+};
+
+struct SortNetData {
+	StringName::AlphCompare compare;
+	bool operator()(const ScriptNetData &p_a, const ScriptNetData &p_b) const {
+		return compare(p_a.name, p_b.name);
+	}
+};
+
 class ScriptServer {
 	enum {
 
@@ -154,6 +169,18 @@ public:
 
 	virtual bool is_placeholder_fallback_enabled() const { return false; }
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const = 0;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const = 0;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const = 0;
+	virtual uint16_t get_rset_property_id(const StringName &p_property) const = 0;
+	virtual StringName get_rset_property(const uint16_t p_rset_property_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_rpc_method_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const = 0;
+
 	Script() {}
 };
 
@@ -195,7 +222,16 @@ public:
 	virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid);
 	virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid);
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const = 0;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const = 0;
+	virtual StringName get_rpc_method(uint16_t p_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const = 0;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const = 0;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const = 0;
+	virtual StringName get_rset_property(uint16_t p_id) const = 0;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const = 0;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const = 0;
 
 	virtual ScriptLanguage *get_language() = 0;
@@ -409,7 +445,16 @@ public:
 	virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = NULL);
 	virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = NULL);
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const { return Vector<ScriptNetData>(); }
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(uint16_t p_id) const { return StringName(); }
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const { return MultiplayerAPI::RPC_MODE_DISABLED; }
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const { return MultiplayerAPI::RPC_MODE_DISABLED; }
+
+	virtual Vector<ScriptNetData> get_rset_properties() const { return Vector<ScriptNetData>(); }
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(uint16_t p_id) const { return StringName(); }
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const { return MultiplayerAPI::RPC_MODE_DISABLED; }
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const { return MultiplayerAPI::RPC_MODE_DISABLED; }
 
 	PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner);

+ 28 - 0
modules/gdnative/nativescript/godot_nativescript.cpp

@@ -36,6 +36,7 @@
 #include "core/project_settings.h"
 #include "core/variant.h"
 #include "gdnative/gdnative.h"
+#include <stdint.h>
 
 #include "nativescript.h"
 
@@ -67,6 +68,14 @@ void GDAPI godot_nativescript_register_class(void *p_gdnative_handle, const char
 	if (classes->has(p_base)) {
 		desc.base_data = &(*classes)[p_base];
 		desc.base_native_type = desc.base_data->base_native_type;
+
+		const NativeScriptDesc *b = desc.base_data;
+		while (b) {
+			desc.rpc_count += b->rpc_count;
+			desc.rset_count += b->rset_count;
+			b = b->base_data;
+		}
+
 	} else {
 		desc.base_data = NULL;
 		desc.base_native_type = p_base;
@@ -87,10 +96,20 @@ void GDAPI godot_nativescript_register_tool_class(void *p_gdnative_handle, const
 	desc.destroy_func = p_destroy_func;
 	desc.is_tool = true;
 	desc.base = p_base;
+	desc.rpc_count = 0;
+	desc.rset_count = 0;
 
 	if (classes->has(p_base)) {
 		desc.base_data = &(*classes)[p_base];
 		desc.base_native_type = desc.base_data->base_native_type;
+
+		const NativeScriptDesc *b = desc.base_data;
+		while (b) {
+			desc.rpc_count += b->rpc_count;
+			desc.rset_count += b->rset_count;
+			b = b->base_data;
+		}
+
 	} else {
 		desc.base_data = NULL;
 		desc.base_native_type = p_base;
@@ -109,6 +128,11 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha
 	NativeScriptDesc::Method method;
 	method.method = p_method;
 	method.rpc_mode = p_attr.rpc_type;
+	method.rpc_method_id = UINT16_MAX;
+	if (p_attr.rpc_type != GODOT_METHOD_RPC_MODE_DISABLED) {
+		method.rpc_method_id = E->get().rpc_count;
+		E->get().rpc_count += 1;
+	}
 	method.info = MethodInfo(p_function_name);
 
 	E->get().methods.insert(p_function_name, method);
@@ -125,6 +149,10 @@ void GDAPI godot_nativescript_register_property(void *p_gdnative_handle, const c
 	property.default_value = *(Variant *)&p_attr->default_value;
 	property.getter = p_get_func;
 	property.rset_mode = p_attr->rset_type;
+	if (p_attr->rset_type != GODOT_METHOD_RPC_MODE_DISABLED) {
+		property.rset_property_id = E->get().rset_count;
+		E->get().rset_count += 1;
+	}
 	property.setter = p_set_func;
 	property.info = PropertyInfo((Variant::Type)p_attr->type,
 			p_path,

+ 286 - 56
modules/gdnative/nativescript/nativescript.cpp

@@ -30,6 +30,8 @@
 
 #include "nativescript.h"
 
+#include <stdint.h>
+
 #include "gdnative/gdnative.h"
 
 #include "core/core_string_names.h"
@@ -402,6 +404,262 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const {
 	}
 }
 
+Vector<ScriptNetData> NativeScript::get_rpc_methods() const {
+
+	Vector<ScriptNetData> v;
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.front(); E; E = E->next()) {
+			if (E->get().rpc_mode != GODOT_METHOD_RPC_MODE_DISABLED) {
+				ScriptNetData nd;
+				nd.name = E->key();
+				nd.mode = MultiplayerAPI::RPCMode(E->get().rpc_mode);
+				v.push_back(nd);
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return v;
+}
+
+uint16_t NativeScript::get_rpc_method_id(const StringName &p_method) const {
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
+		if (E) {
+			return E->get().rpc_method_id;
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return UINT16_MAX;
+}
+
+StringName NativeScript::get_rpc_method(uint16_t p_id) const {
+	ERR_FAIL_COND_V(p_id == UINT16_MAX, StringName());
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.front(); E; E = E->next()) {
+			if (E->get().rpc_method_id == p_id) {
+				return E->key();
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return StringName();
+}
+
+MultiplayerAPI::RPCMode NativeScript::get_rpc_mode_by_id(uint16_t p_id) const {
+
+	ERR_FAIL_COND_V(p_id == UINT16_MAX, MultiplayerAPI::RPC_MODE_DISABLED);
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.front(); E; E = E->next()) {
+			if (E->get().rpc_method_id == p_id) {
+				switch (E->get().rpc_mode) {
+					case GODOT_METHOD_RPC_MODE_DISABLED:
+						return MultiplayerAPI::RPC_MODE_DISABLED;
+					case GODOT_METHOD_RPC_MODE_REMOTE:
+						return MultiplayerAPI::RPC_MODE_REMOTE;
+					case GODOT_METHOD_RPC_MODE_MASTER:
+						return MultiplayerAPI::RPC_MODE_MASTER;
+					case GODOT_METHOD_RPC_MODE_PUPPET:
+						return MultiplayerAPI::RPC_MODE_PUPPET;
+					case GODOT_METHOD_RPC_MODE_REMOTESYNC:
+						return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+					case GODOT_METHOD_RPC_MODE_MASTERSYNC:
+						return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+					case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
+						return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+					default:
+						return MultiplayerAPI::RPC_MODE_DISABLED;
+				}
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+MultiplayerAPI::RPCMode NativeScript::get_rpc_mode(const StringName &p_method) const {
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
+		if (E) {
+			switch (E->get().rpc_mode) {
+				case GODOT_METHOD_RPC_MODE_DISABLED:
+					return MultiplayerAPI::RPC_MODE_DISABLED;
+				case GODOT_METHOD_RPC_MODE_REMOTE:
+					return MultiplayerAPI::RPC_MODE_REMOTE;
+				case GODOT_METHOD_RPC_MODE_MASTER:
+					return MultiplayerAPI::RPC_MODE_MASTER;
+				case GODOT_METHOD_RPC_MODE_PUPPET:
+					return MultiplayerAPI::RPC_MODE_PUPPET;
+				case GODOT_METHOD_RPC_MODE_REMOTESYNC:
+					return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+				case GODOT_METHOD_RPC_MODE_MASTERSYNC:
+					return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+				case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
+					return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+				default:
+					return MultiplayerAPI::RPC_MODE_DISABLED;
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+Vector<ScriptNetData> NativeScript::get_rset_properties() const {
+	Vector<ScriptNetData> v;
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.front(); E; E = E.next()) {
+			if (E.get().rset_mode != GODOT_METHOD_RPC_MODE_DISABLED) {
+				ScriptNetData nd;
+				nd.name = E.key();
+				nd.mode = MultiplayerAPI::RPCMode(E.get().rset_mode);
+				v.push_back(nd);
+			}
+		}
+		script_data = script_data->base_data;
+	}
+
+	return v;
+}
+
+uint16_t NativeScript::get_rset_property_id(const StringName &p_variable) const {
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.find(p_variable);
+		if (E) {
+			return E.get().rset_property_id;
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return UINT16_MAX;
+}
+
+StringName NativeScript::get_rset_property(uint16_t p_id) const {
+	ERR_FAIL_COND_V(p_id == UINT16_MAX, StringName());
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.front(); E; E = E.next()) {
+			if (E.get().rset_property_id == p_id) {
+				return E.key();
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return StringName();
+}
+
+MultiplayerAPI::RPCMode NativeScript::get_rset_mode_by_id(uint16_t p_id) const {
+
+	ERR_FAIL_COND_V(p_id == UINT16_MAX, MultiplayerAPI::RPC_MODE_DISABLED);
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.front(); E; E = E.next()) {
+			if (E.get().rset_property_id == p_id) {
+				switch (E.get().rset_mode) {
+					case GODOT_METHOD_RPC_MODE_DISABLED:
+						return MultiplayerAPI::RPC_MODE_DISABLED;
+					case GODOT_METHOD_RPC_MODE_REMOTE:
+						return MultiplayerAPI::RPC_MODE_REMOTE;
+					case GODOT_METHOD_RPC_MODE_MASTER:
+						return MultiplayerAPI::RPC_MODE_MASTER;
+					case GODOT_METHOD_RPC_MODE_PUPPET:
+						return MultiplayerAPI::RPC_MODE_PUPPET;
+					case GODOT_METHOD_RPC_MODE_REMOTESYNC:
+						return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+					case GODOT_METHOD_RPC_MODE_MASTERSYNC:
+						return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+					case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
+						return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+					default:
+						return MultiplayerAPI::RPC_MODE_DISABLED;
+				}
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+MultiplayerAPI::RPCMode NativeScript::get_rset_mode(const StringName &p_variable) const {
+
+	NativeScriptDesc *script_data = get_script_desc();
+
+	while (script_data) {
+
+		OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.find(p_variable);
+		if (E) {
+			switch (E.get().rset_mode) {
+				case GODOT_METHOD_RPC_MODE_DISABLED:
+					return MultiplayerAPI::RPC_MODE_DISABLED;
+				case GODOT_METHOD_RPC_MODE_REMOTE:
+					return MultiplayerAPI::RPC_MODE_REMOTE;
+				case GODOT_METHOD_RPC_MODE_MASTER:
+					return MultiplayerAPI::RPC_MODE_MASTER;
+				case GODOT_METHOD_RPC_MODE_PUPPET:
+					return MultiplayerAPI::RPC_MODE_PUPPET;
+				case GODOT_METHOD_RPC_MODE_REMOTESYNC:
+					return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+				case GODOT_METHOD_RPC_MODE_MASTERSYNC:
+					return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+				case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
+					return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+				default:
+					return MultiplayerAPI::RPC_MODE_DISABLED;
+			}
+		}
+
+		script_data = script_data->base_data;
+	}
+
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
 String NativeScript::get_class_documentation() const {
 	NativeScriptDesc *script_data = get_script_desc();
 
@@ -803,72 +1061,44 @@ Ref<Script> NativeScriptInstance::get_script() const {
 	return script;
 }
 
-MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_method) const {
-
-	NativeScriptDesc *script_data = GET_SCRIPT_DESC();
-
-	while (script_data) {
+Vector<ScriptNetData> NativeScriptInstance::get_rpc_methods() const {
+	return script->get_rpc_methods();
+}
 
-		Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
-		if (E) {
-			switch (E->get().rpc_mode) {
-				case GODOT_METHOD_RPC_MODE_DISABLED:
-					return MultiplayerAPI::RPC_MODE_DISABLED;
-				case GODOT_METHOD_RPC_MODE_REMOTE:
-					return MultiplayerAPI::RPC_MODE_REMOTE;
-				case GODOT_METHOD_RPC_MODE_MASTER:
-					return MultiplayerAPI::RPC_MODE_MASTER;
-				case GODOT_METHOD_RPC_MODE_PUPPET:
-					return MultiplayerAPI::RPC_MODE_PUPPET;
-				case GODOT_METHOD_RPC_MODE_REMOTESYNC:
-					return MultiplayerAPI::RPC_MODE_REMOTESYNC;
-				case GODOT_METHOD_RPC_MODE_MASTERSYNC:
-					return MultiplayerAPI::RPC_MODE_MASTERSYNC;
-				case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
-					return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
-				default:
-					return MultiplayerAPI::RPC_MODE_DISABLED;
-			}
-		}
+uint16_t NativeScriptInstance::get_rpc_method_id(const StringName &p_method) const {
+	return script->get_rpc_method_id(p_method);
+}
 
-		script_data = script_data->base_data;
-	}
+StringName NativeScriptInstance::get_rpc_method(uint16_t p_id) const {
+	return script->get_rpc_method(p_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode_by_id(uint16_t p_id) const {
+	return script->get_rpc_mode_by_id(p_id);
 }
 
-MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_variable) const {
+MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_method) const {
+	return script->get_rpc_mode(p_method);
+}
 
-	NativeScriptDesc *script_data = GET_SCRIPT_DESC();
+Vector<ScriptNetData> NativeScriptInstance::get_rset_properties() const {
+	return script->get_rset_properties();
+}
 
-	while (script_data) {
+uint16_t NativeScriptInstance::get_rset_property_id(const StringName &p_variable) const {
+	return script->get_rset_property_id(p_variable);
+}
 
-		OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.find(p_variable);
-		if (E) {
-			switch (E.get().rset_mode) {
-				case GODOT_METHOD_RPC_MODE_DISABLED:
-					return MultiplayerAPI::RPC_MODE_DISABLED;
-				case GODOT_METHOD_RPC_MODE_REMOTE:
-					return MultiplayerAPI::RPC_MODE_REMOTE;
-				case GODOT_METHOD_RPC_MODE_MASTER:
-					return MultiplayerAPI::RPC_MODE_MASTER;
-				case GODOT_METHOD_RPC_MODE_PUPPET:
-					return MultiplayerAPI::RPC_MODE_PUPPET;
-				case GODOT_METHOD_RPC_MODE_REMOTESYNC:
-					return MultiplayerAPI::RPC_MODE_REMOTESYNC;
-				case GODOT_METHOD_RPC_MODE_MASTERSYNC:
-					return MultiplayerAPI::RPC_MODE_MASTERSYNC;
-				case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
-					return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
-				default:
-					return MultiplayerAPI::RPC_MODE_DISABLED;
-			}
-		}
+StringName NativeScriptInstance::get_rset_property(uint16_t p_id) const {
+	return script->get_rset_property(p_id);
+}
 
-		script_data = script_data->base_data;
-	}
+MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode_by_id(uint16_t p_id) const {
+	return script->get_rset_mode_by_id(p_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_variable) const {
+	return script->get_rset_mode(p_variable);
 }
 
 ScriptLanguage *NativeScriptInstance::get_language() {

+ 29 - 0
modules/gdnative/nativescript/nativescript.h

@@ -54,6 +54,7 @@ struct NativeScriptDesc {
 		godot_instance_method method;
 		MethodInfo info;
 		int rpc_mode;
+		uint16_t rpc_method_id;
 		String documentation;
 	};
 	struct Property {
@@ -62,6 +63,7 @@ struct NativeScriptDesc {
 		PropertyInfo info;
 		Variant default_value;
 		int rset_mode;
+		uint16_t rset_property_id;
 		String documentation;
 	};
 
@@ -70,7 +72,9 @@ struct NativeScriptDesc {
 		String documentation;
 	};
 
+	uint16_t rpc_count;
 	Map<StringName, Method> methods;
+	uint16_t rset_count;
 	OrderedHashMap<StringName, Property> properties;
 	Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals
 	StringName base;
@@ -86,7 +90,9 @@ struct NativeScriptDesc {
 	bool is_tool;
 
 	inline NativeScriptDesc() :
+			rpc_count(0),
 			methods(),
+			rset_count(0),
 			properties(),
 			signals_(),
 			base(),
@@ -174,6 +180,18 @@ public:
 	virtual void get_script_method_list(List<MethodInfo> *p_list) const;
 	virtual void get_script_property_list(List<PropertyInfo> *p_list) const;
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+
 	String get_class_documentation() const;
 	String get_method_documentation(const StringName &p_method) const;
 	String get_signal_documentation(const StringName &p_signal_name) const;
@@ -210,8 +228,19 @@ public:
 	virtual void notification(int p_notification);
 	String to_string(bool *r_valid);
 	virtual Ref<Script> get_script() const;
+
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+
 	virtual ScriptLanguage *get_language();
 
 	virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);

+ 32 - 0
modules/gdnative/pluginscript/pluginscript_instance.cpp

@@ -93,10 +93,42 @@ void PluginScriptInstance::notification(int p_notification) {
 	_desc->notification(_data, p_notification);
 }
 
+Vector<ScriptNetData> PluginScriptInstance::get_rpc_methods() const {
+	return _script->get_rpc_methods();
+}
+
+uint16_t PluginScriptInstance::get_rpc_method_id(const StringName &p_variable) const {
+	return _script->get_rpc_method_id(p_variable);
+}
+
+StringName PluginScriptInstance::get_rpc_method(uint16_t p_id) const {
+	return _script->get_rpc_method(p_id);
+}
+
+MultiplayerAPI::RPCMode PluginScriptInstance::get_rpc_mode_by_id(uint16_t p_id) const {
+	return _script->get_rpc_mode_by_id(p_id);
+}
+
 MultiplayerAPI::RPCMode PluginScriptInstance::get_rpc_mode(const StringName &p_method) const {
 	return _script->get_rpc_mode(p_method);
 }
 
+Vector<ScriptNetData> PluginScriptInstance::get_rset_properties() const {
+	return _script->get_rset_properties();
+}
+
+uint16_t PluginScriptInstance::get_rset_property_id(const StringName &p_variable) const {
+	return _script->get_rset_property_id(p_variable);
+}
+
+StringName PluginScriptInstance::get_rset_property(uint16_t p_id) const {
+	return _script->get_rset_property(p_id);
+}
+
+MultiplayerAPI::RPCMode PluginScriptInstance::get_rset_mode_by_id(uint16_t p_id) const {
+	return _script->get_rset_mode_by_id(p_id);
+}
+
 MultiplayerAPI::RPCMode PluginScriptInstance::get_rset_mode(const StringName &p_variable) const {
 	return _script->get_rset_mode(p_variable);
 }

+ 9 - 0
modules/gdnative/pluginscript/pluginscript_instance.h

@@ -76,7 +76,16 @@ public:
 
 	void set_path(const String &p_path);
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(uint16_t p_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	virtual void refcount_incremented();

+ 86 - 19
modules/gdnative/pluginscript/pluginscript_script.cpp

@@ -34,6 +34,8 @@
 #include "pluginscript_instance.h"
 #include "pluginscript_script.h"
 
+#include <stdint.h>
+
 #ifdef DEBUG_ENABLED
 #define __ASSERT_SCRIPT_REASON "Cannot retrieve PluginScript class for this script, is your code correct?"
 #define ASSERT_SCRIPT_VALID()                                       \
@@ -298,18 +300,31 @@ Error PluginScript::reload(bool p_keep_state) {
 		_member_lines[*key] = (*members)[*key];
 	}
 	Array *methods = (Array *)&manifest.methods;
+	_rpc_methods.clear();
+	_rpc_variables.clear();
+	if (_ref_base_parent.is_valid()) {
+		_rpc_methods = _ref_base_parent->get_rpc_methods();
+		_rpc_variables = _ref_base_parent->get_rset_properties();
+	}
 	for (int i = 0; i < methods->size(); ++i) {
 		Dictionary v = (*methods)[i];
 		MethodInfo mi = MethodInfo::from_dict(v);
 		_methods_info[mi.name] = mi;
 		// rpc_mode is passed as an optional field and is not part of MethodInfo
 		Variant var = v["rpc_mode"];
-		if (var == Variant()) {
-			_methods_rpc_mode[mi.name] = MultiplayerAPI::RPC_MODE_DISABLED;
-		} else {
-			_methods_rpc_mode[mi.name] = MultiplayerAPI::RPCMode(int(var));
+		if (var != Variant()) {
+			ScriptNetData nd;
+			nd.name = mi.name;
+			nd.mode = MultiplayerAPI::RPCMode(int(var));
+			if (_rpc_methods.find(nd) == -1) {
+				_rpc_methods.push_back(nd);
+			}
 		}
 	}
+
+	// Sort so we are 100% that they are always the same.
+	_rpc_methods.sort_custom<SortNetData>();
+
 	Array *signals = (Array *)&manifest.signals;
 	for (int i = 0; i < signals->size(); ++i) {
 		Variant v = (*signals)[i];
@@ -324,13 +339,19 @@ Error PluginScript::reload(bool p_keep_state) {
 		_properties_default_values[pi.name] = v["default_value"];
 		// rset_mode is passed as an optional field and is not part of PropertyInfo
 		Variant var = v["rset_mode"];
-		if (var == Variant()) {
-			_methods_rpc_mode[pi.name] = MultiplayerAPI::RPC_MODE_DISABLED;
-		} else {
-			_methods_rpc_mode[pi.name] = MultiplayerAPI::RPCMode(int(var));
+		if (var != Variant()) {
+			ScriptNetData nd;
+			nd.name = pi.name;
+			nd.mode = MultiplayerAPI::RPCMode(int(var));
+			if (_rpc_variables.find(nd) == -1) {
+				_rpc_variables.push_back(nd);
+			}
 		}
 	}
 
+	// Sort so we are 100% that they are always the same.
+	_rpc_variables.sort_custom<SortNetData>();
+
 #ifdef TOOLS_ENABLED
 /*for (Set<PlaceHolderScriptInstance*>::Element *E=placeholders.front();E;E=E->next()) {
 
@@ -455,24 +476,70 @@ int PluginScript::get_member_line(const StringName &p_member) const {
 		return -1;
 }
 
-MultiplayerAPI::RPCMode PluginScript::get_rpc_mode(const StringName &p_method) const {
+Vector<ScriptNetData> PluginScript::get_rpc_methods() const {
+	return _rpc_methods;
+}
+
+uint16_t PluginScript::get_rpc_method_id(const StringName &p_method) const {
+	ASSERT_SCRIPT_VALID_V(UINT16_MAX);
+	for (int i = 0; i < _rpc_methods.size(); i++) {
+		if (_rpc_methods[i].name == p_method) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName PluginScript::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	ASSERT_SCRIPT_VALID_V(StringName());
+	if (p_rpc_method_id >= _rpc_methods.size())
+		return StringName();
+	return _rpc_methods[p_rpc_method_id].name;
+}
+
+MultiplayerAPI::RPCMode PluginScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
 	ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED);
-	const Map<StringName, MultiplayerAPI::RPCMode>::Element *e = _methods_rpc_mode.find(p_method);
-	if (e != NULL) {
-		return e->get();
-	} else {
+	if (p_rpc_method_id >= _rpc_methods.size())
 		return MultiplayerAPI::RPC_MODE_DISABLED;
+	return _rpc_methods[p_rpc_method_id].mode;
+}
+
+MultiplayerAPI::RPCMode PluginScript::get_rpc_mode(const StringName &p_method) const {
+	ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED);
+	return get_rpc_mode_by_id(get_rpc_method_id(p_method));
+}
+
+Vector<ScriptNetData> PluginScript::get_rset_properties() const {
+	return _rpc_variables;
+}
+
+uint16_t PluginScript::get_rset_property_id(const StringName &p_property) const {
+	ASSERT_SCRIPT_VALID_V(UINT16_MAX);
+	for (int i = 0; i < _rpc_variables.size(); i++) {
+		if (_rpc_variables[i].name == p_property) {
+			return i;
+		}
 	}
+	return UINT16_MAX;
 }
 
-MultiplayerAPI::RPCMode PluginScript::get_rset_mode(const StringName &p_variable) const {
+StringName PluginScript::get_rset_property(const uint16_t p_rset_property_id) const {
+	ASSERT_SCRIPT_VALID_V(StringName());
+	if (p_rset_property_id >= _rpc_variables.size())
+		return StringName();
+	return _rpc_variables[p_rset_property_id].name;
+}
+
+MultiplayerAPI::RPCMode PluginScript::get_rset_mode_by_id(const uint16_t p_rset_property_id) const {
 	ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED);
-	const Map<StringName, MultiplayerAPI::RPCMode>::Element *e = _variables_rset_mode.find(p_variable);
-	if (e != NULL) {
-		return e->get();
-	} else {
+	if (p_rset_property_id >= _rpc_variables.size())
 		return MultiplayerAPI::RPC_MODE_DISABLED;
-	}
+	return _rpc_variables[p_rset_property_id].mode;
+}
+
+MultiplayerAPI::RPCMode PluginScript::get_rset_mode(const StringName &p_variable) const {
+	ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED);
+	return get_rset_mode_by_id(get_rset_property_id(p_variable));
 }
 
 PluginScript::PluginScript() :

+ 13 - 4
modules/gdnative/pluginscript/pluginscript_script.h

@@ -60,8 +60,8 @@ private:
 	Map<StringName, PropertyInfo> _properties_info;
 	Map<StringName, MethodInfo> _signals_info;
 	Map<StringName, MethodInfo> _methods_info;
-	Map<StringName, MultiplayerAPI::RPCMode> _variables_rset_mode;
-	Map<StringName, MultiplayerAPI::RPCMode> _methods_rpc_mode;
+	Vector<ScriptNetData> _rpc_methods;
+	Vector<ScriptNetData> _rpc_variables;
 
 	Set<Object *> _instances;
 	//exported members
@@ -118,8 +118,17 @@ public:
 
 	virtual int get_member_line(const StringName &p_member) const;
 
-	MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
-	MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_property) const;
+	virtual StringName get_rset_property(const uint16_t p_rset_property_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	PluginScript();
 	void init(PluginScriptLanguage *language);

+ 131 - 24
modules/gdscript/gdscript.cpp

@@ -30,6 +30,8 @@
 
 #include "gdscript.h"
 
+#include <stdint.h>
+
 #include "core/core_string_names.h"
 #include "core/engine.h"
 #include "core/global_constants.h"
@@ -610,6 +612,53 @@ Error GDScript::reload(bool p_keep_state) {
 		_set_subclass_path(E->get(), path);
 	}
 
+	// Copy the base rpc methods so we don't mask their IDs.
+	rpc_functions.clear();
+	rpc_variables.clear();
+	if (base.is_valid()) {
+		rpc_functions = base->rpc_functions;
+		rpc_variables = base->rpc_variables;
+	}
+
+	GDScript *cscript = this;
+	Map<StringName, Ref<GDScript> >::Element *sub_E = subclasses.front();
+	while (cscript) {
+		// RPC Methods
+		for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) {
+			if (E->get()->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) {
+				ScriptNetData nd;
+				nd.name = E->key();
+				nd.mode = E->get()->get_rpc_mode();
+				if (-1 == rpc_functions.find(nd)) {
+					rpc_functions.push_back(nd);
+				}
+			}
+		}
+		// RSet
+		for (Map<StringName, MemberInfo>::Element *E = cscript->member_indices.front(); E; E = E->next()) {
+			if (E->get().rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) {
+				ScriptNetData nd;
+				nd.name = E->key();
+				nd.mode = E->get().rpc_mode;
+				if (-1 == rpc_variables.find(nd)) {
+					rpc_variables.push_back(nd);
+				}
+			}
+		}
+
+		if (cscript != this)
+			sub_E = sub_E->next();
+
+		if (sub_E)
+			cscript = sub_E->get().ptr();
+		else
+			cscript = NULL;
+	}
+
+	// Sort so we are 100% that they are always the same.
+	rpc_functions.sort_custom<SortNetData>();
+	rpc_variables.sort_custom<SortNetData>();
+
 	return OK;
 }
 
@@ -635,6 +684,60 @@ void GDScript::get_members(Set<StringName> *p_members) {
 	}
 }
 
+Vector<ScriptNetData> GDScript::get_rpc_methods() const {
+	return rpc_functions;
+}
+
+uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const {
+	for (int i = 0; i < rpc_functions.size(); i++) {
+		if (rpc_functions[i].name == p_method) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), StringName());
+	return rpc_functions[p_rpc_method_id].name;
+}
+
+MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rpc_method_id].mode;
+}
+
+MultiplayerAPI::RPCMode GDScript::get_rpc_mode(const StringName &p_method) const {
+	return get_rpc_mode_by_id(get_rpc_method_id(p_method));
+}
+
+Vector<ScriptNetData> GDScript::get_rset_properties() const {
+	return rpc_variables;
+}
+
+uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const {
+	for (int i = 0; i < rpc_variables.size(); i++) {
+		if (rpc_variables[i].name == p_variable) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const {
+	ERR_FAIL_COND_V(p_rset_member_id >= rpc_variables.size(), StringName());
+	return rpc_variables[p_rset_member_id].name;
+}
+
+MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const {
+	ERR_FAIL_COND_V(p_rset_member_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rset_member_id].mode;
+}
+
+MultiplayerAPI::RPCMode GDScript::get_rset_mode(const StringName &p_variable) const {
+	return get_rset_mode_by_id(get_rset_property_id(p_variable));
+}
+
 Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
 
 	GDScript *top = this;
@@ -1291,40 +1394,44 @@ ScriptLanguage *GDScriptInstance::get_language() {
 	return GDScriptLanguage::get_singleton();
 }
 
-MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const {
+Vector<ScriptNetData> GDScriptInstance::get_rpc_methods() const {
+	return script->get_rpc_methods();
+}
 
-	const GDScript *cscript = script.ptr();
+uint16_t GDScriptInstance::get_rpc_method_id(const StringName &p_method) const {
+	return script->get_rpc_method_id(p_method);
+}
 
-	while (cscript) {
-		const Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.find(p_method);
-		if (E) {
+StringName GDScriptInstance::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_method(p_rpc_method_id);
+}
 
-			if (E->get()->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) {
-				return E->get()->get_rpc_mode();
-			}
-		}
-		cscript = cscript->_base;
-	}
+MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_mode_by_id(p_rpc_method_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const {
+	return script->get_rpc_mode(p_method);
 }
 
-MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const {
+Vector<ScriptNetData> GDScriptInstance::get_rset_properties() const {
+	return script->get_rset_properties();
+}
 
-	const GDScript *cscript = script.ptr();
+uint16_t GDScriptInstance::get_rset_property_id(const StringName &p_variable) const {
+	return script->get_rset_property_id(p_variable);
+}
 
-	while (cscript) {
-		const Map<StringName, GDScript::MemberInfo>::Element *E = cscript->member_indices.find(p_variable);
-		if (E) {
+StringName GDScriptInstance::get_rset_property(const uint16_t p_rset_member_id) const {
+	return script->get_rset_property(p_rset_member_id);
+}
 
-			if (E->get().rpc_mode) {
-				return E->get().rpc_mode;
-			}
-		}
-		cscript = cscript->_base;
-	}
+MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode_by_id(const uint16_t p_rset_member_id) const {
+	return script->get_rset_mode_by_id(p_rset_member_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const {
+	return script->get_rset_mode(p_variable);
 }
 
 void GDScriptInstance::reload_members() {

+ 23 - 0
modules/gdscript/gdscript.h

@@ -85,6 +85,8 @@ class GDScript : public Script {
 	Map<StringName, MemberInfo> member_indices; //members are just indices to the instanced script.
 	Map<StringName, Ref<GDScript> > subclasses;
 	Map<StringName, Vector<StringName> > _signals;
+	Vector<ScriptNetData> rpc_functions;
+	Vector<ScriptNetData> rpc_variables;
 
 #ifdef TOOLS_ENABLED
 
@@ -213,6 +215,18 @@ public:
 	virtual void get_constants(Map<StringName, Variant> *p_constants);
 	virtual void get_members(Set<StringName> *p_members);
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+
 #ifdef TOOLS_ENABLED
 	virtual bool is_placeholder_fallback_enabled() const { return placeholder_fallback_enabled; }
 #endif
@@ -264,7 +278,16 @@ public:
 
 	void reload_members();
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	GDScriptInstance();

+ 166 - 49
modules/mono/csharp_script.cpp

@@ -31,6 +31,7 @@
 #include "csharp_script.h"
 
 #include <mono/metadata/threads.h>
+#include <stdint.h>
 
 #include "core/io/json.h"
 #include "core/os/file_access.h"
@@ -1979,67 +1980,44 @@ bool CSharpInstance::refcount_decremented() {
 	return ref_dying;
 }
 
-MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(IMonoClassMember *p_member) const {
-
-	if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
-		return MultiplayerAPI::RPC_MODE_REMOTE;
-	if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
-		return MultiplayerAPI::RPC_MODE_MASTER;
-	if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute)))
-		return MultiplayerAPI::RPC_MODE_PUPPET;
-	if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute)))
-		return MultiplayerAPI::RPC_MODE_PUPPET;
-	if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute)))
-		return MultiplayerAPI::RPC_MODE_REMOTESYNC;
-	if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
-		return MultiplayerAPI::RPC_MODE_REMOTESYNC;
-	if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute)))
-		return MultiplayerAPI::RPC_MODE_MASTERSYNC;
-	if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute)))
-		return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+Vector<ScriptNetData> CSharpInstance::get_rpc_methods() const {
+	return script->get_rpc_methods();
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+uint16_t CSharpInstance::get_rpc_method_id(const StringName &p_method) const {
+	return script->get_rpc_method_id(p_method);
 }
 
-MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const {
+StringName CSharpInstance::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_method(p_rpc_method_id);
+}
 
-	GD_MONO_SCOPE_THREAD_ATTACH;
+MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_mode_by_id(p_rpc_method_id);
+}
 
-	GDMonoClass *top = script->script_class;
+MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const {
+	return script->get_rpc_mode(p_method);
+}
 
-	while (top && top != script->native) {
-		GDMonoMethod *method = top->get_fetched_method_unknown_params(p_method);
+Vector<ScriptNetData> CSharpInstance::get_rset_properties() const {
+	return script->get_rset_properties();
+}
 
-		if (method && !method->is_static())
-			return _member_get_rpc_mode(method);
+uint16_t CSharpInstance::get_rset_property_id(const StringName &p_variable) const {
+	return script->get_rset_property_id(p_variable);
+}
 
-		top = top->get_parent_class();
-	}
+StringName CSharpInstance::get_rset_property(const uint16_t p_rset_member_id) const {
+	return script->get_rset_property(p_rset_member_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode_by_id(const uint16_t p_rset_member_id) const {
+	return script->get_rset_mode_by_id(p_rset_member_id);
 }
 
 MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const {
-
-	GD_MONO_SCOPE_THREAD_ATTACH;
-
-	GDMonoClass *top = script->script_class;
-
-	while (top && top != script->native) {
-		GDMonoField *field = top->get_field(p_variable);
-
-		if (field && !field->is_static())
-			return _member_get_rpc_mode(field);
-
-		GDMonoProperty *property = top->get_property(p_variable);
-
-		if (property && !property->is_static())
-			return _member_get_rpc_mode(property);
-
-		top = top->get_parent_class();
-	}
-
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+	return script->get_rset_mode(p_variable);
 }
 
 void CSharpInstance::notification(int p_notification) {
@@ -3251,6 +3229,69 @@ Error CSharpScript::reload(bool p_keep_state) {
 			_update_exports();
 		}
 
+		rpc_functions.clear();
+		rpc_variables.clear();
+
+		GDMonoClass *top = script_class;
+		while (top && top != native) {
+			{
+				Vector<GDMonoMethod *> methods = top->get_all_methods();
+				for (int i = 0; i < methods.size(); i++) {
+					if (!methods[i]->is_static()) {
+						MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(methods[i]);
+						if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+							ScriptNetData nd;
+							nd.name = methods[i]->get_name();
+							nd.mode = mode;
+							if (-1 == rpc_functions.find(nd)) {
+								rpc_functions.push_back(nd);
+							}
+						}
+					}
+				}
+			}
+
+			{
+				Vector<GDMonoField *> fields = top->get_all_fields();
+				for (int i = 0; i < fields.size(); i++) {
+					if (!fields[i]->is_static()) {
+						MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(fields[i]);
+						if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+							ScriptNetData nd;
+							nd.name = fields[i]->get_name();
+							nd.mode = mode;
+							if (-1 == rpc_variables.find(nd)) {
+								rpc_variables.push_back(nd);
+							}
+						}
+					}
+				}
+			}
+
+			{
+				Vector<GDMonoProperty *> properties = top->get_all_properties();
+				for (int i = 0; i < properties.size(); i++) {
+					if (!properties[i]->is_static()) {
+						MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(properties[i]);
+						if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+							ScriptNetData nd;
+							nd.name = properties[i]->get_name();
+							nd.mode = mode;
+							if (-1 == rpc_variables.find(nd)) {
+								rpc_variables.push_back(nd);
+							}
+						}
+					}
+				}
+			}
+
+			top = top->get_parent_class();
+		}
+
+		// Sort so we are 100% that they are always the same.
+		rpc_functions.sort_custom<SortNetData>();
+		rpc_variables.sort_custom<SortNetData>();
+
 		return OK;
 	}
 
@@ -3324,6 +3365,82 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
 	return -1;
 }
 
+MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
+
+	if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
+		return MultiplayerAPI::RPC_MODE_REMOTE;
+	if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
+		return MultiplayerAPI::RPC_MODE_MASTER;
+	if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute)))
+		return MultiplayerAPI::RPC_MODE_PUPPET;
+	if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute)))
+		return MultiplayerAPI::RPC_MODE_PUPPET;
+	if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute)))
+		return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+	if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
+		return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+	if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute)))
+		return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+	if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute)))
+		return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+Vector<ScriptNetData> CSharpScript::get_rpc_methods() const {
+	return rpc_functions;
+}
+
+uint16_t CSharpScript::get_rpc_method_id(const StringName &p_method) const {
+	for (int i = 0; i < rpc_functions.size(); i++) {
+		if (rpc_functions[i].name == p_method) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName CSharpScript::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), StringName());
+	return rpc_functions[p_rpc_method_id].name;
+}
+
+MultiplayerAPI::RPCMode CSharpScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rpc_method_id].mode;
+}
+
+MultiplayerAPI::RPCMode CSharpScript::get_rpc_mode(const StringName &p_method) const {
+	return get_rpc_mode_by_id(get_rpc_method_id(p_method));
+}
+
+Vector<ScriptNetData> CSharpScript::get_rset_properties() const {
+	return rpc_variables;
+}
+
+uint16_t CSharpScript::get_rset_property_id(const StringName &p_variable) const {
+	for (int i = 0; i < rpc_variables.size(); i++) {
+		if (rpc_variables[i].name == p_variable) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName CSharpScript::get_rset_property(const uint16_t p_rset_member_id) const {
+	ERR_FAIL_COND_V(p_rset_member_id >= rpc_variables.size(), StringName());
+	return rpc_variables[p_rset_member_id].name;
+}
+
+MultiplayerAPI::RPCMode CSharpScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const {
+	ERR_FAIL_COND_V(p_rset_member_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rset_member_id].mode;
+}
+
+MultiplayerAPI::RPCMode CSharpScript::get_rset_mode(const StringName &p_variable) const {
+	return get_rset_mode_by_id(get_rset_property_id(p_variable));
+}
+
 Error CSharpScript::load_source_code(const String &p_path) {
 
 	Error ferr = read_all_file_utf8(p_path, source);

+ 26 - 2
modules/mono/csharp_script.h

@@ -113,6 +113,9 @@ class CSharpScript : public Script {
 	Map<StringName, Vector<Argument> > _signals;
 	bool signals_invalidated;
 
+	Vector<ScriptNetData> rpc_functions;
+	Vector<ScriptNetData> rpc_variables;
+
 #ifdef TOOLS_ENABLED
 	List<PropertyInfo> exported_members_cache; // members_cache
 	Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache
@@ -146,6 +149,8 @@ class CSharpScript : public Script {
 	static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
 	static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
 
+	MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
+
 protected:
 	static void _bind_methods();
 
@@ -187,6 +192,18 @@ public:
 
 	virtual int get_member_line(const StringName &p_member) const;
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+
 #ifdef TOOLS_ENABLED
 	virtual bool is_placeholder_fallback_enabled() const { return placeholder_fallback_enabled; }
 #endif
@@ -232,8 +249,6 @@ class CSharpInstance : public ScriptInstance {
 
 	void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
 
-	MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
-
 	void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state);
 
 public:
@@ -265,7 +280,16 @@ public:
 	virtual void refcount_incremented();
 	virtual bool refcount_decremented();
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_variable) const;
+	virtual StringName get_rset_property(const uint16_t p_variable_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	virtual void notification(int p_notification);

+ 109 - 16
modules/visual_script/visual_script.cpp

@@ -30,6 +30,8 @@
 
 #include "visual_script.h"
 
+#include <stdint.h>
+
 #include "core/core_string_names.h"
 #include "core/os/os.h"
 #include "core/project_settings.h"
@@ -1102,6 +1104,60 @@ bool VisualScript::are_subnodes_edited() const {
 }
 #endif
 
+Vector<ScriptNetData> VisualScript::get_rpc_methods() const {
+	return rpc_functions;
+}
+
+uint16_t VisualScript::get_rpc_method_id(const StringName &p_method) const {
+	for (int i = 0; i < rpc_functions.size(); i++) {
+		if (rpc_functions[i].name == p_method) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName VisualScript::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), StringName());
+	return rpc_functions[p_rpc_method_id].name;
+}
+
+MultiplayerAPI::RPCMode VisualScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rpc_method_id].mode;
+}
+
+MultiplayerAPI::RPCMode VisualScript::get_rpc_mode(const StringName &p_method) const {
+	return get_rpc_mode_by_id(get_rpc_method_id(p_method));
+}
+
+Vector<ScriptNetData> VisualScript::get_rset_properties() const {
+	return rpc_variables;
+}
+
+uint16_t VisualScript::get_rset_property_id(const StringName &p_variable) const {
+	for (int i = 0; i < rpc_variables.size(); i++) {
+		if (rpc_variables[i].name == p_variable) {
+			return i;
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName VisualScript::get_rset_property(const uint16_t p_rset_property_id) const {
+	ERR_FAIL_COND_V(p_rset_property_id >= rpc_variables.size(), StringName());
+	return rpc_variables[p_rset_property_id].name;
+}
+
+MultiplayerAPI::RPCMode VisualScript::get_rset_mode_by_id(const uint16_t p_rset_variable_id) const {
+	ERR_FAIL_COND_V(p_rset_variable_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED);
+	return rpc_functions[p_rset_variable_id].mode;
+}
+
+MultiplayerAPI::RPCMode VisualScript::get_rset_mode(const StringName &p_variable) const {
+	return get_rset_mode_by_id(get_rset_property_id(p_variable));
+}
+
 void VisualScript::_set_data(const Dictionary &p_data) {
 
 	Dictionary d = p_data;
@@ -1206,6 +1262,30 @@ void VisualScript::_set_data(const Dictionary &p_data) {
 		is_tool_script = d["is_tool_script"];
 	else
 		is_tool_script = false;
+
+	// Takes all the rpc methods
+	rpc_functions.clear();
+	rpc_variables.clear();
+	for (Map<StringName, Function>::Element *E = functions.front(); E; E = E->next()) {
+		if (E->get().function_id >= 0 && E->get().nodes.find(E->get().function_id)) {
+			Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node;
+			if (vsf.is_valid()) {
+				if (vsf->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) {
+					ScriptNetData nd;
+					nd.name = E->key();
+					nd.mode = vsf->get_rpc_mode();
+					if (rpc_functions.find(nd) == -1) {
+						rpc_functions.push_back(nd);
+					}
+				}
+			}
+		}
+	}
+
+	// Visual script doesn't have rset :(
+
+	// Sort so we are 100% that they are always the same.
+	rpc_functions.sort_custom<SortNetData>();
 }
 
 Dictionary VisualScript::_get_data() const {
@@ -2043,31 +2123,44 @@ Ref<Script> VisualScriptInstance::get_script() const {
 	return script;
 }
 
-MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const {
+Vector<ScriptNetData> VisualScriptInstance::get_rpc_methods() const {
+	return script->get_rpc_methods();
+}
 
-	if (p_method == script->get_default_func())
-		return MultiplayerAPI::RPC_MODE_DISABLED;
+uint16_t VisualScriptInstance::get_rpc_method_id(const StringName &p_method) const {
+	return script->get_rpc_method_id(p_method);
+}
 
-	const Map<StringName, VisualScript::Function>::Element *E = script->functions.find(p_method);
-	if (!E) {
-		return MultiplayerAPI::RPC_MODE_DISABLED;
-	}
+StringName VisualScriptInstance::get_rpc_method(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_method(p_rpc_method_id);
+}
 
-	if (E->get().function_id >= 0 && E->get().nodes.has(E->get().function_id)) {
+MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	return script->get_rpc_mode_by_id(p_rpc_method_id);
+}
 
-		Ref<VisualScriptFunction> vsf = E->get().nodes[E->get().function_id].node;
-		if (vsf.is_valid()) {
+MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const {
+	return script->get_rpc_mode(p_method);
+}
 
-			return vsf->get_rpc_mode();
-		}
-	}
+Vector<ScriptNetData> VisualScriptInstance::get_rset_properties() const {
+	return script->get_rset_properties();
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+uint16_t VisualScriptInstance::get_rset_property_id(const StringName &p_variable) const {
+	return script->get_rset_property_id(p_variable);
 }
 
-MultiplayerAPI::RPCMode VisualScriptInstance::get_rset_mode(const StringName &p_variable) const {
+StringName VisualScriptInstance::get_rset_property(const uint16_t p_rset_property_id) const {
+	return script->get_rset_property(p_rset_property_id);
+}
 
-	return MultiplayerAPI::RPC_MODE_DISABLED;
+MultiplayerAPI::RPCMode VisualScriptInstance::get_rset_mode_by_id(const uint16_t p_rset_variable_id) const {
+	return script->get_rset_mode_by_id(p_rset_variable_id);
+}
+
+MultiplayerAPI::RPCMode VisualScriptInstance::get_rset_mode(const StringName &p_variable) const {
+	return script->get_rset_mode(p_variable);
 }
 
 void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_owner) {

+ 23 - 0
modules/visual_script/visual_script.h

@@ -245,6 +245,8 @@ private:
 	Map<StringName, Function> functions;
 	Map<StringName, Variable> variables;
 	Map<StringName, Vector<Argument> > custom_signals;
+	Vector<ScriptNetData> rpc_functions;
+	Vector<ScriptNetData> rpc_variables;
 
 	Map<Object *, VisualScriptInstance *> instances;
 
@@ -362,6 +364,18 @@ public:
 
 	virtual int get_member_line(const StringName &p_member) const;
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_property) const;
+	virtual StringName get_rset_property(const uint16_t p_rset_property_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+
 #ifdef TOOLS_ENABLED
 	virtual bool are_subnodes_edited() const;
 #endif
@@ -441,7 +455,16 @@ public:
 
 	virtual ScriptLanguage *get_language();
 
+	virtual Vector<ScriptNetData> get_rpc_methods() const;
+	virtual uint16_t get_rpc_method_id(const StringName &p_method) const;
+	virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const;
+	virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
 	virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
+
+	virtual Vector<ScriptNetData> get_rset_properties() const;
+	virtual uint16_t get_rset_property_id(const StringName &p_property) const;
+	virtual StringName get_rset_property(const uint16_t p_rset_property_id) const;
+	virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_rpc_method_id) const;
 	virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	VisualScriptInstance();

+ 114 - 14
scene/main/node.cpp

@@ -30,6 +30,8 @@
 
 #include "node.h"
 
+#include <stdint.h>
+
 #include "core/core_string_names.h"
 #include "core/io/resource_loader.h"
 #include "core/message_queue.h"
@@ -498,22 +500,38 @@ bool Node::is_network_master() const {
 
 /***** RPC CONFIG ********/
 
-void Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode) {
+uint16_t Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode) {
 
-	if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) {
-		data.rpc_methods.erase(p_method);
+	uint16_t mid = get_node_rpc_method_id(p_method);
+	if (mid == UINT16_MAX) {
+		// It's new
+		NetData nd;
+		nd.name = p_method;
+		nd.mode = p_mode;
+		data.rpc_methods.push_back(nd);
+		return ((uint16_t)data.rpc_properties.size() - 1) | (1 << 15);
 	} else {
-		data.rpc_methods[p_method] = p_mode;
-	};
+		int c_mid = (~(1 << 15)) & mid;
+		data.rpc_methods.write[c_mid].mode = p_mode;
+		return mid;
+	}
 }
 
-void Node::rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode) {
+uint16_t Node::rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode) {
 
-	if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) {
-		data.rpc_properties.erase(p_property);
+	uint16_t pid = get_node_rset_property_id(p_property);
+	if (pid == UINT16_MAX) {
+		// It's new
+		NetData nd;
+		nd.name = p_property;
+		nd.mode = p_mode;
+		data.rpc_properties.push_back(nd);
+		return ((uint16_t)data.rpc_properties.size() - 1) | (1 << 15);
 	} else {
-		data.rpc_properties[p_property] = p_mode;
-	};
+		int c_pid = (~(1 << 15)) & pid;
+		data.rpc_properties.write[c_pid].mode = p_mode;
+		return pid;
+	}
 }
 
 /***** RPC FUNCTIONS ********/
@@ -731,12 +749,94 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {
 	multiplayer = p_multiplayer;
 }
 
-const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) {
-	return data.rpc_methods.find(p_method);
+uint16_t Node::get_node_rpc_method_id(const StringName &p_method) const {
+	for (int i = 0; i < data.rpc_methods.size(); i++) {
+		if (data.rpc_methods[i].name == p_method) {
+			// Returns `i` with the high bit set to 1 so we know that this id comes
+			// from the node and not the script.
+			return i | (1 << 15);
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName Node::get_node_rpc_method(const uint16_t p_rpc_method_id) const {
+	// Make sure this is a node generated ID.
+	if (((1 << 15) & p_rpc_method_id) > 0) {
+		int mid = (~(1 << 15)) & p_rpc_method_id;
+		if (mid < data.rpc_methods.size())
+			return data.rpc_methods[mid].name;
+	}
+	return StringName();
+}
+
+MultiplayerAPI::RPCMode Node::get_node_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
+	// Make sure this is a node generated ID.
+	if (((1 << 15) & p_rpc_method_id) > 0) {
+		int mid = (~(1 << 15)) & p_rpc_method_id;
+		if (mid < data.rpc_methods.size())
+			return data.rpc_methods[mid].mode;
+	}
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+MultiplayerAPI::RPCMode Node::get_node_rpc_mode(const StringName &p_method) const {
+	return get_node_rpc_mode_by_id(get_node_rpc_method_id(p_method));
 }
 
-const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) {
-	return data.rpc_properties.find(p_property);
+uint16_t Node::get_node_rset_property_id(const StringName &p_property) const {
+	for (int i = 0; i < data.rpc_properties.size(); i++) {
+		if (data.rpc_properties[i].name == p_property) {
+			// Returns `i` with the high bit set to 1 so we know that this id comes
+			// from the node and not the script.
+			return i | (1 << 15);
+		}
+	}
+	return UINT16_MAX;
+}
+
+StringName Node::get_node_rset_property(const uint16_t p_rset_property_id) const {
+	// Make sure this is a node generated ID.
+	if (((1 << 15) & p_rset_property_id) > 0) {
+		int mid = (~(1 << 15)) & p_rset_property_id;
+		if (mid < data.rpc_properties.size())
+			return data.rpc_properties[mid].name;
+	}
+	return StringName();
+}
+
+MultiplayerAPI::RPCMode Node::get_node_rset_mode_by_id(const uint16_t p_rset_property_id) const {
+	if (((1 << 15) & p_rset_property_id) > 0) {
+		int mid = (~(1 << 15)) & p_rset_property_id;
+		if (mid < data.rpc_properties.size())
+			return data.rpc_properties[mid].mode;
+	}
+	return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+
+MultiplayerAPI::RPCMode Node::get_node_rset_mode(const StringName &p_property) const {
+	return get_node_rset_mode_by_id(get_node_rset_property_id(p_property));
+}
+
+String Node::get_rpc_md5() const {
+	String rpc_list;
+	for (int i = 0; i < data.rpc_methods.size(); i += 1) {
+		rpc_list += String(data.rpc_methods[i].name);
+	}
+	for (int i = 0; i < data.rpc_properties.size(); i += 1) {
+		rpc_list += String(data.rpc_properties[i].name);
+	}
+	if (get_script_instance()) {
+		Vector<ScriptNetData> rpc = get_script_instance()->get_rpc_methods();
+		for (int i = 0; i < rpc.size(); i += 1) {
+			rpc_list += String(rpc[i].name);
+		}
+		rpc = get_script_instance()->get_rset_properties();
+		for (int i = 0; i < rpc.size(); i += 1) {
+			rpc_list += String(rpc[i].name);
+		}
+	}
+	return rpc_list.md5_text();
 }
 
 bool Node::can_process_notification(int p_what) const {

+ 25 - 6
scene/main/node.h

@@ -85,6 +85,11 @@ private:
 		GroupData() { persistent = false; }
 	};
 
+	struct NetData {
+		StringName name;
+		MultiplayerAPI::RPCMode mode;
+	};
+
 	struct Data {
 
 		String filename;
@@ -118,8 +123,8 @@ private:
 		Node *pause_owner;
 
 		int network_master;
-		Map<StringName, MultiplayerAPI::RPCMode> rpc_methods;
-		Map<StringName, MultiplayerAPI::RPCMode> rpc_properties;
+		Vector<NetData> rpc_methods;
+		Vector<NetData> rpc_properties;
 
 		// variables used to properly sort the node when processing, ignored otherwise
 		//should move all the stuff below to bits
@@ -427,8 +432,8 @@ public:
 	int get_network_master() const;
 	bool is_network_master() const;
 
-	void rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode); // config a local method for RPC
-	void rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode); // config a local property for RPC
+	uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode); // config a local method for RPC
+	uint16_t rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode); // config a local property for RPC
 
 	void rpc(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode
 	void rpc_unreliable(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode
@@ -446,8 +451,22 @@ public:
 	Ref<MultiplayerAPI> get_multiplayer() const;
 	Ref<MultiplayerAPI> get_custom_multiplayer() const;
 	void set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer);
-	const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rpc_mode(const StringName &p_method);
-	const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rset_mode(const StringName &p_property);
+
+	/// Returns the rpc method ID, otherwise UINT32_MAX
+	uint16_t get_node_rpc_method_id(const StringName &p_method) const;
+	StringName get_node_rpc_method(const uint16_t p_rpc_method_id) const;
+	MultiplayerAPI::RPCMode get_node_rpc_mode_by_id(const uint16_t p_rpc_method_id) const;
+	MultiplayerAPI::RPCMode get_node_rpc_mode(const StringName &p_method) const;
+
+	/// Returns the rpc property ID, otherwise UINT32_MAX
+	uint16_t get_node_rset_property_id(const StringName &p_property) const;
+	StringName get_node_rset_property(const uint16_t p_rset_property_id) const;
+	MultiplayerAPI::RPCMode get_node_rset_mode_by_id(const uint16_t p_rpc_method_id) const;
+	MultiplayerAPI::RPCMode get_node_rset_mode(const StringName &p_property) const;
+
+	/// Can be used to check if the rpc methods and the rset properties are the
+	/// same across the peers.
+	String get_rpc_md5() const;
 
 	Node();
 	~Node();