diff options
Diffstat (limited to 'core/io')
37 files changed, 386 insertions, 2518 deletions
diff --git a/core/io/compression.h b/core/io/compression.h index cbfed74124..06f26876e5 100644 --- a/core/io/compression.h +++ b/core/io/compression.h @@ -54,8 +54,6 @@ public: static int get_max_compressed_buffer_size(int p_src_size, Mode p_mode = MODE_ZSTD); static int decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD); static int decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode); - - Compression() {} }; #endif // COMPRESSION_H diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index 49fa73dab2..33f992e153 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -183,7 +183,9 @@ Error ConfigFile::_internal_save(FileAccess *file) { if (E != values.front()) { file->store_string("\n"); } - file->store_string("[" + E.key() + "]\n\n"); + if (E.key() != "") { + file->store_string("[" + E.key() + "]\n\n"); + } for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) { String vstr; diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index e54c947340..df631053b8 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -233,7 +233,7 @@ uint64_t FileAccessCompressed::get_position() const { if (writing) { return write_pos; } else { - return read_block * block_size + read_pos; + return (uint64_t)read_block * block_size + read_pos; } } diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 7b43daf9c0..b2832b2a75 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -110,8 +110,8 @@ PackedData::PackedData() { } void PackedData::_free_packed_dirs(PackedDir *p_dir) { - for (Map<String, PackedDir *>::Element *E = p_dir->subdirs.front(); E; E = E->next()) { - _free_packed_dirs(E->get()); + for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) { + _free_packed_dirs(E.value); } memdelete(p_dir); } @@ -395,8 +395,8 @@ Error DirAccessPack::list_dir_begin() { list_dirs.clear(); list_files.clear(); - for (Map<String, PackedData::PackedDir *>::Element *E = current->subdirs.front(); E; E = E->next()) { - list_dirs.push_back(E->key()); + for (const KeyValue<String, PackedData::PackedDir *> &E : current->subdirs) { + list_dirs.push_back(E.key); } for (Set<String>::Element *E = current->files.front(); E; E = E->next()) { diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index b5c882e9ce..53bf7456e6 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -99,7 +99,7 @@ static int godot_testerror(voidpf opaque, voidpf stream) { } static voidpf godot_alloc(voidpf opaque, uInt items, uInt size) { - return memalloc(items * size); + return memalloc((size_t)items * size); } static void godot_free(voidpf opaque, voidpf address) { diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 5c1352c1b6..5c67bc4c48 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -49,6 +49,14 @@ HTTPClient *HTTPClient::create() { return nullptr; } +void HTTPClient::set_http_proxy(const String &p_host, int p_port) { + WARN_PRINT("HTTP proxy feature is not available"); +} + +void HTTPClient::set_https_proxy(const String &p_host, int p_port) { + WARN_PRINT("HTTPS proxy feature is not available"); +} + Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { int size = p_body.size(); return request(p_method, p_url, p_headers, size > 0 ? p_body.ptr() : nullptr, size); @@ -85,8 +93,7 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { } } } - query.erase(0, 1); - return query; + return query.substr(1); } Dictionary HTTPClient::_get_response_headers_as_dictionary() { @@ -143,6 +150,9 @@ void HTTPClient::_bind_methods() { ClassDB::bind_method(D_METHOD("get_status"), &HTTPClient::get_status); ClassDB::bind_method(D_METHOD("poll"), &HTTPClient::poll); + ClassDB::bind_method(D_METHOD("set_http_proxy", "host", "port"), &HTTPClient::set_http_proxy); + ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPClient::set_https_proxy); + ClassDB::bind_method(D_METHOD("query_string_from_dict", "fields"), &HTTPClient::query_string_from_dict); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_mode_enabled"), "set_blocking_mode", "is_blocking_mode_enabled"); diff --git a/core/io/http_client.h b/core/io/http_client.h index 718c3a905e..9bc0134a6a 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -192,6 +192,10 @@ public: virtual Error poll() = 0; + // Use empty string or -1 to unset + virtual void set_http_proxy(const String &p_host, int p_port); + virtual void set_https_proxy(const String &p_host, int p_port); + HTTPClient() {} virtual ~HTTPClient() {} }; diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index b3d35b3603..f2ddeddbb8 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -70,9 +70,21 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss connection = tcp_connection; - if (conn_host.is_valid_ip_address()) { + if (ssl && https_proxy_port != -1) { + proxy_client.instantiate(); // Needs proxy negotiation + server_host = https_proxy_host; + server_port = https_proxy_port; + } else if (!ssl && http_proxy_port != -1) { + server_host = http_proxy_host; + server_port = http_proxy_port; + } else { + server_host = conn_host; + server_port = conn_port; + } + + if (server_host.is_valid_ip_address()) { // Host contains valid IP - Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port); + Error err = tcp_connection->connect_to_host(IPAddress(server_host), server_port); if (err) { status = STATUS_CANT_CONNECT; return err; @@ -81,7 +93,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss status = STATUS_CONNECTING; } else { // Host contains hostname and needs to be resolved to IP - resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host); + resolving = IP::get_singleton()->resolve_hostname_queue_item(server_host); status = STATUS_RESOLVING; } @@ -134,7 +146,12 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector< ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); - String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n"; + String uri = p_url; + if (!ssl && http_proxy_port != -1) { + uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url); + } + + String request = String(_methods[p_method]) + " " + uri + " HTTP/1.1\r\n"; bool add_host = true; bool add_clen = p_body_size > 0; bool add_uagent = true; @@ -229,6 +246,7 @@ void HTTPClientTCP::close() { } connection.unref(); + proxy_client.unref(); status = STATUS_DISCONNECTED; head_request = false; if (resolving != IP::RESOLVER_INVALID_ID) { @@ -265,7 +283,7 @@ Error HTTPClientTCP::poll() { Error err = ERR_BUG; // Should be at least one entry. while (ip_candidates.size() > 0) { - err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port); if (err == OK) { break; } @@ -294,7 +312,48 @@ Error HTTPClientTCP::poll() { return OK; } break; case StreamPeerTCP::STATUS_CONNECTED: { - if (ssl) { + if (ssl && proxy_client.is_valid()) { + Error err = proxy_client->poll(); + if (err == ERR_UNCONFIGURED) { + proxy_client->set_connection(tcp_connection); + const Vector<String> headers; + err = proxy_client->request(METHOD_CONNECT, vformat("%s:%d", conn_host, conn_port), headers, nullptr, 0); + if (err != OK) { + status = STATUS_CANT_CONNECT; + return err; + } + } else if (err != OK) { + status = STATUS_CANT_CONNECT; + return err; + } + switch (proxy_client->get_status()) { + case STATUS_REQUESTING: { + return OK; + } break; + case STATUS_BODY: { + proxy_client->read_response_body_chunk(); + return OK; + } break; + case STATUS_CONNECTED: { + if (proxy_client->get_response_code() != RESPONSE_OK) { + status = STATUS_CANT_CONNECT; + return ERR_CANT_CONNECT; + } + proxy_client.unref(); + return OK; + } + case STATUS_DISCONNECTED: + case STATUS_RESOLVING: + case STATUS_CONNECTING: { + status = STATUS_CANT_CONNECT; + ERR_FAIL_V(ERR_BUG); + } break; + default: { + status = STATUS_CANT_CONNECT; + return ERR_CANT_CONNECT; + } break; + } + } else if (ssl) { Ref<StreamPeerSSL> ssl; if (!handshaking) { // Connect the StreamPeerSSL and start handshaking @@ -344,7 +403,7 @@ Error HTTPClientTCP::poll() { Error err = ERR_CANT_CONNECT; while (ip_candidates.size() > 0) { tcp_connection->disconnect_from_host(); - err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port); if (err == OK) { return OK; } @@ -678,6 +737,26 @@ int HTTPClientTCP::get_read_chunk_size() const { return read_chunk_size; } +void HTTPClientTCP::set_http_proxy(const String &p_host, int p_port) { + if (p_host.is_empty() || p_port == -1) { + http_proxy_host = ""; + http_proxy_port = -1; + } else { + http_proxy_host = p_host; + http_proxy_port = p_port; + } +} + +void HTTPClientTCP::set_https_proxy(const String &p_host, int p_port) { + if (p_host.is_empty() || p_port == -1) { + https_proxy_host = ""; + https_proxy_port = -1; + } else { + https_proxy_host = p_host; + https_proxy_port = p_port; + } +} + HTTPClientTCP::HTTPClientTCP() { tcp_connection.instantiate(); } diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index 170afb551c..40a962925e 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -38,8 +38,14 @@ private: Status status = STATUS_DISCONNECTED; IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; Array ip_candidates; - int conn_port = -1; + int conn_port = -1; // Server to make requests to String conn_host; + int server_port = -1; // Server to connect to (might be a proxy server) + String server_host; + int http_proxy_port = -1; // Proxy server for http requests + String http_proxy_host; + int https_proxy_port = -1; // Proxy server for https requests + String https_proxy_host; bool ssl = false; bool ssl_verify_host = false; bool blocking = false; @@ -58,6 +64,7 @@ private: Ref<StreamPeerTCP> tcp_connection; Ref<StreamPeer> connection; + Ref<HTTPClientTCP> proxy_client; // Negotiate with proxy server int response_num = 0; Vector<String> response_headers; @@ -87,6 +94,8 @@ public: void set_read_chunk_size(int p_size) override; int get_read_chunk_size() const override; Error poll() override; + void set_http_proxy(const String &p_host, int p_port) override; + void set_https_proxy(const String &p_host, int p_port) override; HTTPClientTCP(); }; diff --git a/core/io/image.cpp b/core/io/image.cpp index 3112dd217f..3f34de132f 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -86,20 +86,14 @@ SaveEXRFunc Image::save_exr_func = nullptr; SavePNGBufferFunc Image::save_png_buffer_func = nullptr; -void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel) { - uint32_t ofs = (p_y * width + p_x) * p_pixelsize; - - for (uint32_t i = 0; i < p_pixelsize; i++) { - p_data[ofs + i] = p_pixel[i]; - } +void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel) { + uint32_t ofs = (p_y * width + p_x) * p_pixel_size; + memcpy(p_data + ofs, p_pixel, p_pixel_size); } -void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixelsize, const uint8_t *p_data, uint8_t *p_pixel) { - uint32_t ofs = (p_y * width + p_x) * p_pixelsize; - - for (uint32_t i = 0; i < p_pixelsize; i++) { - p_pixel[i] = p_data[ofs + i]; - } +void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel) { + uint32_t ofs = (p_y * width + p_x) * p_pixel_size; + memcpy(p_pixel, p_data + ofs, p_pixel_size); } int Image::get_format_pixel_size(Format p_format) { @@ -797,7 +791,7 @@ static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict uint32_t interp_down = p01 + (((p11 - p01) * src_xofs_frac) >> FRAC_BITS); uint32_t interp = interp_up + (((interp_down - interp_up) * src_yofs_frac) >> FRAC_BITS); interp >>= FRAC_BITS; - p_dst[i * p_dst_width * CC + j * CC + l] = interp; + p_dst[i * p_dst_width * CC + j * CC + l] = uint8_t(interp); } else if (sizeof(T) == 2) { //half float float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); @@ -2506,7 +2500,7 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po clipped_src_rect.position.y = ABS(p_dest.y); } - if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + if (clipped_src_rect.has_no_area()) { return; } @@ -2561,7 +2555,7 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co clipped_src_rect.position.y = ABS(p_dest.y); } - if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + if (clipped_src_rect.has_no_area()) { return; } @@ -2615,7 +2609,7 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P clipped_src_rect.position.y = ABS(p_dest.y); } - if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + if (clipped_src_rect.has_no_area()) { return; } @@ -2664,7 +2658,7 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c clipped_src_rect.position.y = ABS(p_dest.y); } - if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + if (clipped_src_rect.has_no_area()) { return; } @@ -2697,24 +2691,55 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c } } -void Image::fill(const Color &c) { +// Repeats `p_pixel` `p_count` times in consecutive memory. +// Results in the original pixel and `p_count - 1` subsequent copies of it. +void Image::_repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count) { + int offset = 1; + for (int stride = 1; offset + stride <= p_count; stride *= 2) { + memcpy(p_pixel + offset * p_pixel_size, p_pixel, stride * p_pixel_size); + offset += stride; + } + if (offset < p_count) { + memcpy(p_pixel + offset * p_pixel_size, p_pixel, (p_count - offset) * p_pixel_size); + } +} + +void Image::fill(const Color &p_color) { ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill in compressed or custom image formats."); - uint8_t *wp = data.ptrw(); - uint8_t *dst_data_ptr = wp; + uint8_t *dst_data_ptr = data.ptrw(); int pixel_size = get_format_pixel_size(format); - // put first pixel with the format-aware API - set_pixel(0, 0, c); + // Put first pixel with the format-aware API. + _set_color_at_ofs(dst_data_ptr, 0, p_color); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - uint8_t *dst = &dst_data_ptr[(y * width + x) * pixel_size]; + _repeat_pixel_over_subsequent_memory(dst_data_ptr, pixel_size, width * height); +} - for (int k = 0; k < pixel_size; k++) { - dst[k] = dst_data_ptr[k]; - } +void Image::fill_rect(const Rect2 &p_rect, const Color &p_color) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill rect in compressed or custom image formats."); + + Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect.abs()); + if (r.has_no_area()) { + return; + } + + uint8_t *dst_data_ptr = data.ptrw(); + + int pixel_size = get_format_pixel_size(format); + + // Put first pixel with the format-aware API. + uint8_t *rect_first_pixel_ptr = &dst_data_ptr[(r.position.y * width + r.position.x) * pixel_size]; + _set_color_at_ofs(rect_first_pixel_ptr, 0, p_color); + + if (r.size.x == width) { + // No need to fill rows separately. + _repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, width * r.size.y); + } else { + _repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, r.size.x); + for (int y = 1; y < r.size.y; y++) { + memcpy(rect_first_pixel_ptr + y * width * pixel_size, rect_first_pixel_ptr, r.size.x * pixel_size); } } } @@ -3160,6 +3185,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("blend_rect", "src", "src_rect", "dst"), &Image::blend_rect); ClassDB::bind_method(D_METHOD("blend_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blend_rect_mask); ClassDB::bind_method(D_METHOD("fill", "color"), &Image::fill); + ClassDB::bind_method(D_METHOD("fill_rect", "rect", "color"), &Image::fill_rect); ClassDB::bind_method(D_METHOD("get_used_rect"), &Image::get_used_rect); ClassDB::bind_method(D_METHOD("get_rect", "rect"), &Image::get_rect); diff --git a/core/io/image.h b/core/io/image.h index 8f1b251ac3..9023463b08 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -41,7 +41,7 @@ * Image storage class. This is used to store an image in user memory, as well as * providing some basic methods for image manipulation. * Images can be loaded from a file, or registered into the Render object as textures. -*/ + */ class Image; @@ -190,8 +190,10 @@ private: static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); bool _can_modify(Format p_format) const; - _FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel); - _FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixelsize, const uint8_t *p_data, uint8_t *p_pixel); + _FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel); + _FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel); + + _FORCE_INLINE_ void _repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count); void _set_data(const Dictionary &p_data); Dictionary _get_data() const; @@ -362,7 +364,8 @@ public: void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest); void blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest); void blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest); - void fill(const Color &c); + void fill(const Color &p_color); + void fill_rect(const Rect2 &p_rect, const Color &p_color); Rect2 get_used_rect() const; Ref<Image> get_rect(const Rect2 &p_area) const; diff --git a/core/io/ip.cpp b/core/io/ip.cpp index e3102508a3..68b4e4b354 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -288,8 +288,8 @@ Array IP::_get_local_interfaces() const { Array results; Map<String, Interface_Info> interfaces; get_local_interfaces(&interfaces); - for (Map<String, Interface_Info>::Element *E = interfaces.front(); E; E = E->next()) { - Interface_Info &c = E->get(); + for (KeyValue<String, Interface_Info> &E : interfaces) { + Interface_Info &c = E.value; Dictionary rc; rc["name"] = c.name; rc["friendly"] = c.name_friendly; @@ -310,8 +310,8 @@ Array IP::_get_local_interfaces() const { void IP::get_local_addresses(List<IPAddress> *r_addresses) const { Map<String, Interface_Info> interfaces; get_local_interfaces(&interfaces); - for (Map<String, Interface_Info>::Element *E = interfaces.front(); E; E = E->next()) { - for (const IPAddress &F : E->get().ip_addresses) { + for (const KeyValue<String, Interface_Info> &E : interfaces) { + for (const IPAddress &F : E.value.ip_addresses) { r_addresses->push_front(F); } } diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 09539f716c..8a8bdf07d3 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -50,7 +50,7 @@ void Logger::set_flush_stdout_on_print(bool value) { _flush_stdout_on_print = value; } -void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { +void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { if (!should_log(true)) { return; } @@ -81,7 +81,11 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c err_details = p_code; } - logf_error("%s: %s\n", err_type, err_details); + if (p_editor_notify) { + logf_error("%s: %s\n", err_type, err_details); + } else { + logf_error("USER %s: %s\n", err_type, err_details); + } logf_error(" at: %s (%s:%i) - %s\n", p_function, p_file, p_line, p_code); } @@ -256,13 +260,13 @@ void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) { } } -void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { +void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { if (!should_log(true)) { return; } for (int i = 0; i < loggers.size(); ++i) { - loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_type); + loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type); } } diff --git a/core/io/logger.h b/core/io/logger.h index ccf68562d6..48b073aa45 100644 --- a/core/io/logger.h +++ b/core/io/logger.h @@ -54,7 +54,7 @@ public: static void set_flush_stdout_on_print(bool value); virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0 = 0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR); void logf(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void logf_error(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -83,7 +83,6 @@ class RotatedFileLogger : public Logger { FileAccess *file = nullptr; - void rotate_file_without_closing(); void close_file(); void clear_old_backups(); void rotate_file(); @@ -103,7 +102,7 @@ public: CompositeLogger(Vector<Logger *> p_loggers); virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR); void add_logger(Logger *p_logger); diff --git a/core/io/marshalls.h b/core/io/marshalls.h index 05804d5a46..9ea060e48c 100644 --- a/core/io/marshalls.h +++ b/core/io/marshalls.h @@ -44,9 +44,9 @@ typedef uint32_t uintr_t; #endif /** - * Miscellaneous helpers for marshalling data types, and encoding - * in an endian independent way - */ + * Miscellaneous helpers for marshalling data types, and encoding + * in an endian independent way + */ union MarshallFloat { uint32_t i; ///< int diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp deleted file mode 100644 index c145225751..0000000000 --- a/core/io/multiplayer_api.cpp +++ /dev/null @@ -1,1150 +0,0 @@ -/*************************************************************************/ -/* multiplayer_api.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "multiplayer_api.h" - -#include "core/debugger/engine_debugger.h" -#include "core/io/marshalls.h" -#include "core/io/multiplayer_replicator.h" -#include "scene/main/node.h" - -#include <stdint.h> - -#ifdef DEBUG_ENABLED -#include "core/os/os.h" -#endif - -String _get_rpc_md5(const Node *p_node) { - String rpc_list; - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - rpc_list += String(node_config[i].name); - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - rpc_list += String(script_config[i].name); - } - } - return rpc_list.md5_text(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config(const Node *p_node, const StringName &p_method, uint16_t &r_id) { - const Vector<MultiplayerAPI::RPCConfig> node_config = p_node->get_node_rpc_methods(); - for (int i = 0; i < node_config.size(); i++) { - if (node_config[i].name == p_method) { - r_id = ((uint16_t)i) | (1 << 15); - return node_config[i]; - } - } - if (p_node->get_script_instance()) { - const Vector<MultiplayerAPI::RPCConfig> script_config = p_node->get_script_instance()->get_rpc_methods(); - for (int i = 0; i < script_config.size(); i++) { - if (script_config[i].name == p_method) { - r_id = (uint16_t)i; - return script_config[i]; - } - } - } - return MultiplayerAPI::RPCConfig(); -} - -const MultiplayerAPI::RPCConfig _get_rpc_config_by_id(Node *p_node, uint16_t p_id) { - Vector<MultiplayerAPI::RPCConfig> config; - uint16_t id = p_id; - if (id & (1 << 15)) { - id = id & ~(1 << 15); - config = p_node->get_node_rpc_methods(); - } else if (p_node->get_script_instance()) { - config = p_node->get_script_instance()->get_rpc_methods(); - } - if (id < config.size()) { - return config[id]; - } - return MultiplayerAPI::RPCConfig(); -} - -_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - return false; - } break; - case MultiplayerAPI::RPC_MODE_ANY: { - return true; - } break; - case MultiplayerAPI::RPC_MODE_AUTHORITY: { - return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); - } break; - } - - return false; -} - -void MultiplayerAPI::poll() { - if (!network_peer.is_valid() || network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { - return; - } - - network_peer->poll(); - - if (!network_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here. - return; - } - - while (network_peer->get_available_packet_count()) { - int sender = network_peer->get_packet_peer(); - const uint8_t *packet; - int len; - - Error err = network_peer->get_packet(&packet, len); - if (err != OK) { - ERR_PRINT("Error getting packet!"); - break; // Something is wrong! - } - - rpc_sender_id = sender; - _process_packet(sender, packet, len); - rpc_sender_id = 0; - - if (!network_peer.is_valid()) { - break; // It's also possible that a packet or RPC caused a disconnection, so also check here. - } - } - if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) { - replicator->poll(); - } -} - -void MultiplayerAPI::clear() { - replicator->clear(); - connected_peers.clear(); - path_get_cache.clear(); - path_send_cache.clear(); - packet_cache.clear(); - last_send_cache_id = 1; -} - -void MultiplayerAPI::set_root_node(Node *p_node) { - root_node = p_node; -} - -Node *MultiplayerAPI::get_root_node() { - return root_node; -} - -void MultiplayerAPI::set_network_peer(const Ref<MultiplayerPeer> &p_peer) { - if (p_peer == network_peer) { - return; // Nothing to do - } - - ERR_FAIL_COND_MSG(p_peer.is_valid() && p_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, - "Supplied MultiplayerPeer must be connecting or connected."); - - if (network_peer.is_valid()) { - network_peer->disconnect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->disconnect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->disconnect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->disconnect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->disconnect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - clear(); - } - - network_peer = p_peer; - - if (network_peer.is_valid()) { - network_peer->connect("peer_connected", callable_mp(this, &MultiplayerAPI::_add_peer)); - network_peer->connect("peer_disconnected", callable_mp(this, &MultiplayerAPI::_del_peer)); - network_peer->connect("connection_succeeded", callable_mp(this, &MultiplayerAPI::_connected_to_server)); - network_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed)); - network_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected)); - } -} - -Ref<MultiplayerPeer> MultiplayerAPI::get_network_peer() const { - return network_peer; -} - -#ifdef DEBUG_ENABLED -void _profile_node_data(const String &p_what, ObjectID p_id) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("node"); - values.push_back(p_id); - values.push_back(p_what); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} - -void _profile_bandwidth_data(const String &p_inout, int p_size) { - if (EngineDebugger::is_profiling("multiplayer")) { - Array values; - values.push_back("bandwidth"); - values.push_back(p_inout); - values.push_back(OS::get_singleton()->get_ticks_msec()); - values.push_back(p_size); - EngineDebugger::profiler_add_frame_data("multiplayer", values); - } -} -#endif - -// Returns the packet size stripping the node path added when the node is not yet cached. -int get_packet_len(uint32_t p_node_target, int p_packet_len) { - if (p_node_target & 0x80000000) { - int ofs = p_node_target & 0x7FFFFFFF; - return p_packet_len - (p_packet_len - ofs); - } else { - return p_packet_len; - } -} - -void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(root_node == nullptr, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it."); - ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("in", p_packet_len); -#endif - - // Extract the `packet_type` from the LSB three bits: - uint8_t packet_type = p_packet[0] & 7; - - switch (packet_type) { - case NETWORK_COMMAND_SIMPLIFY_PATH: { - _process_simplify_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_CONFIRM_PATH: { - _process_confirm_path(p_from, p_packet, p_packet_len); - } break; - - case NETWORK_COMMAND_REMOTE_CALL: { - // 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) >> NODE_ID_COMPRESSION_SHIFT; - int name_id_compression = (p_packet[0] & 32) >> NAME_ID_COMPRESSION_SHIFT; - - 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 == nullptr, "Invalid packet received. Requested node was not found."); - - 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(); - } - - const int packet_len = get_packet_len(node_target, p_packet_len); - _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); - } break; - - case NETWORK_COMMAND_RAW: { - _process_raw(p_from, p_packet, p_packet_len); - } break; - case NETWORK_COMMAND_SPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true); - } break; - case NETWORK_COMMAND_DESPAWN: { - replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false); - } break; - case NETWORK_COMMAND_SYNC: { - replicator->process_sync(p_from, p_packet, p_packet_len); - } break; - } -} - -Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { - Node *node = nullptr; - - if (p_node_target & 0x80000000) { - // Use full path (not cached yet). - int ofs = p_node_target & 0x7FFFFFFF; - - ERR_FAIL_COND_V_MSG(ofs >= p_packet_len, nullptr, "Invalid packet received. Size smaller than declared."); - - String paths; - paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); - - NodePath np = paths; - - node = root_node->get_node(np); - - if (!node) { - ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); - } - return node; - } else { - // Use cached path. - return get_cached_node(p_from, p_node_target); - } -} - -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. - const RPCConfig config = _get_rpc_config_by_id(p_node, p_rpc_method_id); - ERR_FAIL_COND(config.name == StringName()); - - bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_network_authority()) + "."); - - int argc = 0; - bool byte_only = false; - - const bool byte_only_or_no_args = ((p_packet[0] & 64) >> BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - if (byte_only_or_no_args) { - if (p_offset < p_packet_len) { - // This packet contains only bytes. - argc = 1; - byte_only = true; - } else { - // This rpc calls a method without parameters. - } - } else { - // Normal variant, takes the argument count from the packet. - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - argc = p_packet[p_offset]; - p_offset += 1; - } - - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - argp.resize(argc); - -#ifdef DEBUG_ENABLED - _profile_node_data("in_rpc", p_node->get_instance_id()); -#endif - - if (byte_only) { - Vector<uint8_t> pure_data; - const int len = p_packet_len - p_offset; - pure_data.resize(len); - memcpy(pure_data.ptrw(), &p_packet[p_offset], len); - args.write[0] = pure_data; - argp.write[0] = &args[0]; - p_offset += len; - } else { - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - - int vlen; - 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]; - p_offset += vlen; - } - } - - Callable::CallError ce; - - p_node->call(config.name, (const Variant **)argp.ptr(), argc, ce); - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, config.name, (const Variant **)argp.ptr(), argc, ce); - error = "RPC - " + error; - 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 < 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 + ofs), p_packet_len - ofs); - - NodePath path = paths; - - if (!path_get_cache.has(p_from)) { - path_get_cache[p_from] = PathGetCache(); - } - - Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = _get_rpc_md5(node) == 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; - - path_get_cache[p_from].nodes[id] = ni; - - // Encode path to send ack. - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), nullptr); - - Vector<uint8_t> packet; - - packet.resize(1 + 1 + len); - packet.write[0] = NETWORK_COMMAND_CONFIRM_PATH; - packet.write[1] = valid_rpc_checksum; - encode_cstring(pname.get_data(), &packet.write[2]); - - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->set_target_peer(p_from); - network_peer->put_packet(packet.ptr(), packet.size()); -} - -void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { - 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[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."); - - Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); - ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); - E->get() = true; -} - -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. - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_target < 0 && E->get() == -p_target) { - continue; // Continue, excluded. - } - - if (p_target > 0 && E->get() != p_target) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - - if (!F || !F->get()) { - // Path was not cached, or was cached but is unconfirmed. - if (!F) { - // Not cached at all, take note. - peers_to_add.push_back(E->get()); - } - - has_all_peers = false; - } - } - - if (peers_to_add.size() > 0) { - // Those that need to be added, send a message for this. - - // Encode function name. - const CharString path = String(p_path).utf8(); - const int path_len = encode_cstring(path.get_data(), nullptr); - - // Extract MD5 from rpc methods list. - const String methods_md5 = _get_rpc_md5(p_node); - 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]); - - for (int &E : peers_to_add) { - network_peer->set_target_peer(E); // To all of you. - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->put_packet(packet.ptr(), packet.size()); - - psc->confirmed_peers.insert(E, 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); - if (err != OK) { - return err; - } - if (r_buffer) { - // The first byte is not used by the marshalling, 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); - if (err != OK) { - return err; - } - } - - return OK; -} - -void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(network_peer.is_null(), "Attempt to remote call/set when networking is not active in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); - - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); - - ERR_FAIL_COND_MSG(p_argcount > 255, "Too many arguments >255."); - - if (p_to != 0 && !connected_peers.has(ABS(p_to))) { - ERR_FAIL_COND_MSG(p_to == network_peer->get_unique_id(), "Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id()) + "."); - - ERR_FAIL_MSG("Attempt to remote call unexisting ID: " + itos(p_to) + "."); - } - - NodePath from_path = (root_node->get_path()).rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(from_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[from_path] = PathSentCache(); - psc = path_send_cache.getptr(from_path); - 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; - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - - // Encode meta. - // The meta is composed by a single byte that contains (starting from the least significant bit): - // - `NetworkCommands` in the first three bits. - // - `NetworkNodeIdCompression` in the next 2 bits. - // - `NetworkNameIdCompression` in the next 1 bit. - // - `byte_only_or_no_args` in the next 1 bit. - // - So we still have the last bit free! - uint8_t command_type = NETWORK_COMMAND_REMOTE_CALL; - uint8_t node_id_compression = UINT8_MAX; - uint8_t name_id_compression = UINT8_MAX; - bool byte_only_or_no_args = false; - - MAKE_ROOM(1); - // The meta is composed along the way, so just set 0 for now. - packet_cache.write[0] = 0; - ofs += 1; - - // 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 don'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; - } - - // Encode method ID - if (p_rpc_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(p_rpc_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(p_rpc_id, &(packet_cache.write[ofs])); - ofs += 2; - } - - if (p_argcount == 0) { - byte_only_or_no_args = true; - } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) { - byte_only_or_no_args = true; - // Special optimization when only the byte vector is sent. - const Vector<uint8_t> data = *p_arg[0]; - MAKE_ROOM(ofs + data.size()); - memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size()); - ofs += data.size(); - } else { - // Arguments - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = p_argcount; - ofs += 1; - for (int i = 0; i < p_argcount; i++) { - int len(0); - Error err = encode_and_compress_variant(*p_arg[i], nullptr, 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_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 << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT); - -#ifdef DEBUG_ENABLED - _profile_bandwidth_data("out", ofs); -#endif - - // Take chance and set transfer mode, since all send methods will use it. - network_peer->set_transfer_channel(p_config.channel); - network_peer->set_transfer_mode(p_config.transfer_mode); - - if (has_all_peers) { - // They all have verified paths, so send fast. - network_peer->set_target_peer(p_to); // To all of you. - network_peer->put_packet(packet_cache.ptr(), ofs); // A message with love. - } 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. - CharString pname = String(from_path).utf8(); - int path_len = encode_cstring(pname.get_data(), nullptr); - MAKE_ROOM(ofs + path_len); - encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - if (p_to < 0 && E->get() == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && E->get() != p_to) { - continue; // Continue, not for this peer. - } - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - ERR_CONTINUE(!F); // Should never happen. - - network_peer->set_target_peer(E->get()); // To this one specifically. - - if (F->get()) { - // This one confirmed path, so use id. - encode_uint32(psc->id, &(packet_cache.write[1])); - network_peer->put_packet(packet_cache.ptr(), ofs); - } else { - // This one did not confirm path yet, so use entire path (sorry!). - encode_uint32(0x80000000 | ofs, &(packet_cache.write[1])); // Offset to path and flag. - network_peer->put_packet(packet_cache.ptr(), ofs + path_len); - } - } - } -} - -void MultiplayerAPI::_add_peer(int p_id) { - connected_peers.insert(p_id); - path_get_cache.insert(p_id, PathGetCache()); - if (is_network_server()) { - replicator->spawn_all(p_id); - } - emit_signal(SNAME("network_peer_connected"), p_id); -} - -void MultiplayerAPI::_del_peer(int p_id) { - connected_peers.erase(p_id); - // Cleanup get cache. - path_get_cache.erase(p_id); - // Cleanup sent cache. - // Some refactoring is needed to make this faster and do paths GC. - List<NodePath> keys; - path_send_cache.get_key_list(&keys); - for (const NodePath &E : keys) { - PathSentCache *psc = path_send_cache.getptr(E); - psc->confirmed_peers.erase(p_id); - } - emit_signal(SNAME("network_peer_disconnected"), p_id); -} - -void MultiplayerAPI::_connected_to_server() { - emit_signal(SNAME("connected_to_server")); -} - -void MultiplayerAPI::_connection_failed() { - emit_signal(SNAME("connection_failed")); -} - -void MultiplayerAPI::_server_disconnected() { - emit_signal(SNAME("server_disconnected")); -} - -void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to call an RPC while no network peer is active."); - ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to call an RPC on a node which is not inside SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); - - int node_id = network_peer->get_unique_id(); - bool call_local_native = false; - bool call_local_script = false; - uint16_t rpc_id = UINT16_MAX; - const RPCConfig config = _get_rpc_config(p_node, p_method, rpc_id); - ERR_FAIL_COND_MSG(config.name == StringName(), - vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is not marked for RPCs.", p_method, p_node->get_path())); - if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { - if (rpc_id & (1 << 15)) { - call_local_native = config.sync; - } else { - call_local_script = config.sync; - } - } - - if (p_peer_id != node_id) { -#ifdef DEBUG_ENABLED - _profile_node_data("out_rpc", p_node->get_instance_id()); -#endif - - _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); - } - - if (call_local_native) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - p_node->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - if (call_local_script) { - int temp_id = rpc_sender_id; - rpc_sender_id = get_network_unique_id(); - Callable::CallError ce; - ce.error = Callable::CallError::CALL_OK; - p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); - rpc_sender_id = temp_id; - if (ce.error != Callable::CallError::CALL_OK) { - String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in script local call: - " + error + "."; - ERR_PRINT(error); - return; - } - } - - ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode."); -} - -Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) { - ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet."); - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active."); - ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); - - MAKE_ROOM(p_data.size() + 1); - const uint8_t *r = p_data.ptr(); - packet_cache.write[0] = NETWORK_COMMAND_RAW; - memcpy(&packet_cache.write[1], &r[0], p_data.size()); - - network_peer->set_target_peer(p_to); - network_peer->set_transfer_channel(p_channel); - network_peer->set_transfer_mode(p_mode); - - return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); -} - -void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small."); - - Vector<uint8_t> out; - int len = p_packet_len - 1; - out.resize(len); - { - uint8_t *w = out.ptrw(); - memcpy(&w[0], &p_packet[1], len); - } - emit_signal(SNAME("network_peer_packet"), p_from, out); -} - -bool MultiplayerAPI::send_confirm_path(Node *p_node, NodePath p_path, int p_peer_id, int &r_id) { - // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); - if (!psc) { - // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); - psc->id = last_send_cache_id++; - } - r_id = psc->id; - - // See if all peers have cached path (if so, call can be fast). - return _send_confirm_path(p_node, p_path, psc, p_peer_id); -} - -Node *MultiplayerAPI::get_cached_node(int p_from, uint32_t p_node_id) { - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(p_node_id); - ERR_FAIL_COND_V_MSG(!F, nullptr, vformat("ID %d not found in cache of peer %d.", p_node_id, p_from)); - - PathGetCache::NodeInfo *ni = &F->get(); - Node *node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path: " + String(ni->path) + "."); - } - return node; -} - -int MultiplayerAPI::get_network_unique_id() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), 0, "No network peer is assigned. Unable to get unique network ID."); - return network_peer->get_unique_id(); -} - -bool MultiplayerAPI::is_network_server() const { - return network_peer.is_valid() && network_peer->is_server(); -} - -void MultiplayerAPI::set_refuse_new_network_connections(bool p_refuse) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "No network peer is assigned. Unable to set 'refuse_new_connections'."); - network_peer->set_refuse_new_connections(p_refuse); -} - -bool MultiplayerAPI::is_refusing_new_network_connections() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), false, "No network peer is assigned. Unable to get 'refuse_new_connections'."); - return network_peer->is_refusing_new_connections(); -} - -Vector<int> MultiplayerAPI::get_network_connected_peers() const { - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), Vector<int>(), "No network peer is assigned. Assume no peers are connected."); - - Vector<int> ret; - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - ret.push_back(E->get()); - } - - return ret; -} - -void MultiplayerAPI::set_allow_object_decoding(bool p_enable) { - allow_object_decoding = p_enable; -} - -bool MultiplayerAPI::is_object_decoding_allowed() const { - return allow_object_decoding; -} - -MultiplayerReplicator *MultiplayerAPI::get_replicator() const { - return replicator; -} - -void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - replicator->scene_enter_exit_notify(p_scene, p_node, p_enter); -} - -void MultiplayerAPI::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); - ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node); - ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); - ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); - ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); - ClassDB::bind_method(D_METHOD("is_network_server"), &MultiplayerAPI::is_network_server); - ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &MultiplayerAPI::get_rpc_sender_id); - ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &MultiplayerAPI::set_network_peer); - ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); - ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); - - ClassDB::bind_method(D_METHOD("get_network_connected_peers"), &MultiplayerAPI::get_network_connected_peers); - ClassDB::bind_method(D_METHOD("set_refuse_new_network_connections", "refuse"), &MultiplayerAPI::set_refuse_new_network_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); - ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding); - ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed); - ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_network_peer", "get_network_peer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node"); - ADD_PROPERTY_DEFAULT("refuse_new_network_connections", false); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator"); - - ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("network_peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet"))); - ADD_SIGNAL(MethodInfo("connected_to_server")); - ADD_SIGNAL(MethodInfo("connection_failed")); - ADD_SIGNAL(MethodInfo("server_disconnected")); - - BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_ANY); - BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY); -} - -MultiplayerAPI::MultiplayerAPI() { - replicator = memnew(MultiplayerReplicator(this)); - clear(); -} - -MultiplayerAPI::~MultiplayerAPI() { - clear(); - memdelete(replicator); -} diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h deleted file mode 100644 index 3c96a3eed1..0000000000 --- a/core/io/multiplayer_api.h +++ /dev/null @@ -1,182 +0,0 @@ -/*************************************************************************/ -/* multiplayer_api.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MULTIPLAYER_API_H -#define MULTIPLAYER_API_H - -#include "core/io/multiplayer_peer.h" -#include "core/io/resource_uid.h" -#include "core/object/ref_counted.h" - -class MultiplayerReplicator; - -class MultiplayerAPI : public RefCounted { - GDCLASS(MultiplayerAPI, RefCounted); - -public: - enum RPCMode { - RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_ANY, // Any peer can call this rpc() - RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc() - }; - - struct RPCConfig { - StringName name; - RPCMode rpc_mode = RPC_MODE_DISABLED; - bool sync = false; - MultiplayerPeer::TransferMode transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; - int channel = 0; - - bool operator==(RPCConfig const &p_other) const { - return name == p_other.name; - } - }; - - struct SortRPCConfig { - StringName::AlphCompare compare; - bool operator()(const RPCConfig &p_a, const RPCConfig &p_b) const { - return compare(p_a.name, p_b.name); - } - }; - - enum NetworkCommands { - NETWORK_COMMAND_REMOTE_CALL = 0, - NETWORK_COMMAND_SIMPLIFY_PATH, - NETWORK_COMMAND_CONFIRM_PATH, - NETWORK_COMMAND_RAW, - NETWORK_COMMAND_SPAWN, - NETWORK_COMMAND_DESPAWN, - NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn. - }; - - 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 { - NODE_ID_COMPRESSION_SHIFT = 3, - NAME_ID_COMPRESSION_SHIFT = 5, - BYTE_ONLY_OR_NO_ARGS_SHIFT = 6, - }; - -private: - //path sent caches - struct PathSentCache { - Map<int, bool> confirmed_peers; - int id; - }; - - //path get caches - struct PathGetCache { - struct NodeInfo { - NodePath path; - ObjectID instance; - }; - - Map<int, NodeInfo> nodes; - }; - - Ref<MultiplayerPeer> network_peer; - int rpc_sender_id = 0; - Set<int> connected_peers; - HashMap<NodePath, PathSentCache> path_send_cache; - Map<int, PathGetCache> path_get_cache; - int last_send_cache_id; - Vector<uint8_t> packet_cache; - Node *root_node = nullptr; - bool allow_object_decoding = false; - MultiplayerReplicator *replicator = nullptr; - -protected: - static void _bind_methods(); - - 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, 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_raw(int p_from, const uint8_t *p_packet, int p_packet_len); - - void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); - bool _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, int p_target); - -public: - void poll(); - void clear(); - void set_root_node(Node *p_node); - Node *get_root_node(); - void set_network_peer(const Ref<MultiplayerPeer> &p_peer); - Ref<MultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0); - - 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); - - // Called by Node.rpc - void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); - // Called by Node._notification - void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); - // Called by replicator - bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id); - Node *get_cached_node(int p_from, uint32_t p_node_id); - - void _add_peer(int p_id); - void _del_peer(int p_id); - void _connected_to_server(); - void _connection_failed(); - void _server_disconnected(); - - bool has_network_peer() const { return network_peer.is_valid(); } - Vector<int> get_network_connected_peers() const; - int get_rpc_sender_id() const { return rpc_sender_id; } - int get_network_unique_id() const; - bool is_network_server() const; - void set_refuse_new_network_connections(bool p_refuse); - bool is_refusing_new_network_connections() const; - - void set_allow_object_decoding(bool p_enable); - bool is_object_decoding_allowed() const; - - MultiplayerReplicator *get_replicator() const; - - MultiplayerAPI(); - ~MultiplayerAPI(); -}; - -VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); - -#endif // MULTIPLAYER_API_H diff --git a/core/io/multiplayer_peer.cpp b/core/io/multiplayer_peer.cpp deleted file mode 100644 index 83cf24d7e3..0000000000 --- a/core/io/multiplayer_peer.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/*************************************************************************/ -/* multiplayer_peer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "multiplayer_peer.h" - -#include "core/os/os.h" - -uint32_t MultiplayerPeer::generate_unique_id() const { - uint32_t hash = 0; - - while (hash == 0 || hash == 1) { - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_ticks_usec()); - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_unix_time(), hash); - hash = hash_djb2_one_32( - (uint32_t)OS::get_singleton()->get_user_data_dir().hash64(), hash); - hash = hash_djb2_one_32( - (uint32_t)((uint64_t)this), hash); // Rely on ASLR heap - hash = hash_djb2_one_32( - (uint32_t)((uint64_t)&hash), hash); // Rely on ASLR stack - - hash = hash & 0x7FFFFFFF; // Make it compatible with unsigned, since negative ID is used for exclusion - } - - return hash; -} - -void MultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel); - ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel); - ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode); - ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode); - ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer); - - ClassDB::bind_method(D_METHOD("get_packet_peer"), &MultiplayerPeer::get_packet_peer); - - ClassDB::bind_method(D_METHOD("poll"), &MultiplayerPeer::poll); - - ClassDB::bind_method(D_METHOD("get_connection_status"), &MultiplayerPeer::get_connection_status); - ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerPeer::get_unique_id); - ClassDB::bind_method(D_METHOD("generate_unique_id"), &MultiplayerPeer::generate_unique_id); - - ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "enable"), &MultiplayerPeer::set_refuse_new_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerPeer::is_refusing_new_connections); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); - - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE); - BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED); - BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE); - - BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED); - BIND_ENUM_CONSTANT(CONNECTION_CONNECTING); - BIND_ENUM_CONSTANT(CONNECTION_CONNECTED); - - BIND_CONSTANT(TARGET_PEER_BROADCAST); - BIND_CONSTANT(TARGET_PEER_SERVER); - - ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("server_disconnected")); - ADD_SIGNAL(MethodInfo("connection_succeeded")); - ADD_SIGNAL(MethodInfo("connection_failed")); -} diff --git a/core/io/multiplayer_peer.h b/core/io/multiplayer_peer.h deleted file mode 100644 index 7ca4e7930b..0000000000 --- a/core/io/multiplayer_peer.h +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* multiplayer_peer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef NETWORKED_MULTIPLAYER_PEER_H -#define NETWORKED_MULTIPLAYER_PEER_H - -#include "core/io/packet_peer.h" - -class MultiplayerPeer : public PacketPeer { - GDCLASS(MultiplayerPeer, PacketPeer); - -protected: - static void _bind_methods(); - -public: - enum { - TARGET_PEER_BROADCAST = 0, - TARGET_PEER_SERVER = 1 - }; - enum TransferMode { - TRANSFER_MODE_UNRELIABLE, - TRANSFER_MODE_UNRELIABLE_ORDERED, - TRANSFER_MODE_RELIABLE, - }; - - enum ConnectionStatus { - CONNECTION_DISCONNECTED, - CONNECTION_CONNECTING, - CONNECTION_CONNECTED, - }; - - virtual void set_transfer_channel(int p_channel) = 0; - virtual int get_transfer_channel() const = 0; - virtual void set_transfer_mode(TransferMode p_mode) = 0; - virtual TransferMode get_transfer_mode() const = 0; - virtual void set_target_peer(int p_peer_id) = 0; - - virtual int get_packet_peer() const = 0; - - virtual bool is_server() const = 0; - - virtual void poll() = 0; - - virtual int get_unique_id() const = 0; - - virtual void set_refuse_new_connections(bool p_enable) = 0; - virtual bool is_refusing_new_connections() const = 0; - - virtual ConnectionStatus get_connection_status() const = 0; - uint32_t generate_unique_id() const; - - MultiplayerPeer() {} -}; - -VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode) -VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus) - -#endif // NETWORKED_MULTIPLAYER_PEER_H diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp deleted file mode 100644 index b9d0675af1..0000000000 --- a/core/io/multiplayer_replicator.cpp +++ /dev/null @@ -1,788 +0,0 @@ -/*************************************************************************/ -/* multiplayer_replicator.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "core/io/multiplayer_replicator.h" - -#include "core/io/marshalls.h" -#include "scene/main/node.h" -#include "scene/resources/packed_scene.h" - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) \ - packet_cache.resize(m_amount); - -Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) { - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - SceneConfig &cfg = replications[p_scene_id]; - int full_size = 0; - bool same_size = true; - int last_size = 0; - bool all_raw = true; - struct EncodeInfo { - int size = 0; - bool raw = false; - List<Variant> state; - }; - Map<ObjectID, struct EncodeInfo> state; - if (tracked_objects.has(p_scene_id)) { - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - Object *obj = ObjectDB::get_instance(obj_id); - if (obj) { - struct EncodeInfo info; - Error err = _get_state(cfg.sync_properties, obj, info.state); - ERR_CONTINUE(err); - err = _encode_state(info.state, nullptr, info.size, &info.raw); - ERR_CONTINUE(err); - state[obj_id] = info; - full_size += info.size; - if (last_size && info.size != last_size) { - same_size = false; - } - all_raw = all_raw && info.raw; - last_size = info.size; - } - } - } - // Default implementation do not send empty updates. - if (!full_size) { - return OK; - } -#ifdef DEBUG_ENABLED - if (full_size > 4096 && cfg.sync_interval) { - WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id)); - } -#endif - if (same_size) { - // This is fast and small. Should we allow more than 256 objects per type? - // This costs us 1 byte. - MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size); - } else { - MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size); - } - int ofs = 0; - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); - ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ptr[ofs] = cfg.sync_recv++; - ofs += 1; - ofs += encode_uint16(state.size(), &ptr[ofs]); - if (same_size) { - ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]); - } - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - if (!state.has(obj_id)) { - continue; - } - struct EncodeInfo &info = state[obj_id]; - Object *obj = ObjectDB::get_instance(obj_id); - ERR_CONTINUE(!obj); - int size = 0; - if (!same_size) { - // We need to encode the size of every object. - ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]); - } - Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw); - ERR_CONTINUE(err); - ofs += size; - } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); - return network_peer->put_packet(ptr, ofs); -} - -void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received"); - ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id)); - SceneConfig &cfg = replications[p_id]; - ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server"); - const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - int ofs = SYNC_CMD_OFFSET; - int time = p_packet[ofs]; - // Skip old update. - if (time < cfg.sync_recv && cfg.sync_recv - time < 127) { - return; - } - cfg.sync_recv = time; - ofs += 1; - int count = decode_uint16(&p_packet[ofs]); - ofs += 2; -#ifdef DEBUG_ENABLED - ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count); -#else - if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) { - return; - } -#endif - int data_size = 0; - bool raw = false; - if (same_size) { - // This is fast and optimized. - data_size = decode_uint16(&p_packet[ofs]); - raw = (data_size & (1 << 15)) != 0; - data_size = data_size & ~(1 << 15); - ofs += 2; - ERR_FAIL_COND(p_packet_len - ofs < data_size * count); - } - for (const ObjectID &obj_id : tracked_objects[p_id]) { - Object *obj = ObjectDB::get_instance(obj_id); - ERR_CONTINUE(!obj); - if (!same_size) { - // This is slow and wasteful. - data_size = decode_uint16(&p_packet[ofs]); - raw = (data_size & (1 << 15)) != 0; - data_size = data_size & ~(1 << 15); - ofs += 2; - ERR_FAIL_COND(p_packet_len - ofs < data_size); - } - int size = 0; - Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw); - ofs += data_size; - ERR_CONTINUE(err); - ERR_CONTINUE(size != data_size); - } -} - -Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) { - ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - Error err; - // Prepare state - List<Variant> state_variants; - int state_len = 0; - const SceneConfig &cfg = replications[p_scene_id]; - if (p_spawn) { - if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) { - return err; - } - } - - bool is_raw = false; - if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) { - is_raw = true; - const PackedByteArray pba = state_variants[0]; - state_len = pba.size(); - } else if (state_variants.size()) { - err = _encode_state(state_variants, nullptr, state_len); - ERR_FAIL_COND_V(err, err); - } else { - is_raw = true; - } - - int ofs = 0; - - // Prepare simplified path - const Node *root_node = multiplayer->get_root_node(); - ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED); - NodePath rel_path = (root_node->get_path()).rel_path_to(p_path); - const Vector<StringName> names = rel_path.get_names(); - ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER); - - NodePath parent = NodePath(names.subarray(0, names.size() - 2), false); - ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent); - - int path_id = 0; - multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id); - - // Encode name and parent ID. - CharString cname = String(names[names.size() - 1]).utf8(); - int nlen = encode_cstring(cname.get_data(), nullptr); - MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); - ofs = 1; - ofs += encode_uint64(p_scene_id, &ptr[ofs]); - ofs += encode_uint32(path_id, &ptr[ofs]); - ofs += encode_uint32(nlen, &ptr[ofs]); - ofs += encode_cstring(cname.get_data(), &ptr[ofs]); - - // Encode state. - if (!is_raw) { - _encode_state(state_variants, &ptr[ofs], state_len); - } else if (state_len) { - PackedByteArray pba = state_variants[0]; - memcpy(&ptr[ofs], pba.ptr(), state_len); - } - - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, ofs + state_len); -} - -void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received"); - int ofs = SPAWN_CMD_OFFSET; - uint32_t node_target = decode_uint32(&p_packet[ofs]); - Node *parent = multiplayer->get_cached_node(p_from, node_target); - ofs += 4; - ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found."); - - uint32_t name_len = decode_uint32(&p_packet[ofs]); - ofs += 4; - ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len)); - ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size."); - - const String name = String::utf8((const char *)&p_packet[ofs], name_len); - // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names. - ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name)); - ofs += name_len; - - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) { - String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id); - if (p_spawn) { - const bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - - ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name)); - RES res = ResourceLoader::load(scene_path); - ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path); - PackedScene *scene = Object::cast_to<PackedScene>(res.ptr()); - ERR_FAIL_COND(!scene); - Node *node = scene->instantiate(); - ERR_FAIL_COND(!node); - replicated_nodes[node->get_instance_id()] = p_scene_id; - _track(p_scene_id, node); - int size; - _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw); - parent->_add_child_nocheck(node, name); - emit_signal(SNAME("spawned"), p_scene_id, node); - } else { - ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name)); - Node *node = parent->get_node(name); - ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name)); - emit_signal(SNAME("despawned"), p_scene_id, node); - _untrack(p_scene_id, node); - replicated_nodes.erase(node->get_instance_id()); - node->queue_delete(); - } - } else { - PackedByteArray data; - if (p_packet_len > ofs) { - data.resize(p_packet_len - ofs); - memcpy(data.ptrw(), &p_packet[ofs], data.size()); - } - if (p_spawn) { - emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data); - } else { - emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data); - } - } -} - -void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); - ResourceUID::ID id = decode_uint64(&p_packet[1]); - ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); - - const SceneConfig &cfg = replications[id]; - if (cfg.on_spawn_despawn_receive.is_valid()) { - int ofs = SPAWN_CMD_OFFSET; - bool is_raw = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1; - Variant data; - int left = p_packet_len - ofs; - if (is_raw && left) { - PackedByteArray pba; - pba.resize(left); - memcpy(pba.ptrw(), &p_packet[ofs], pba.size()); - data = pba; - } else if (left) { - ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK); - } - - Variant args[4]; - args[0] = p_from; - args[1] = id; - args[2] = data; - args[3] = p_spawn; - const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce); - ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed"); - } else { - _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn); - } -} - -void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) { - ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received"); - ResourceUID::ID id = decode_uint64(&p_packet[1]); - ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id)); - const SceneConfig &cfg = replications[id]; - if (cfg.on_sync_receive.is_valid()) { - Array objs; - if (tracked_objects.has(id)) { - objs.resize(tracked_objects[id].size()); - int idx = 0; - for (const ObjectID &obj_id : tracked_objects[id]) { - objs[idx++] = ObjectDB::get_instance(obj_id); - } - } - PackedByteArray pba; - pba.resize(p_packet_len - SPAWN_CMD_OFFSET); - if (pba.size()) { - memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET); - } - Variant args[4] = { p_from, id, objs, pba }; - Variant *argp[4] = { args, &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce); - ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed"); - } else { - ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client"); - _process_default_sync(id, p_packet, p_packet_len); - } -} - -Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) { - ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object"); - for (const StringName &prop : p_properties) { - bool valid = false; - const Variant v = p_obj->get(prop, &valid); - ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); - r_variant.push_back(v); - } - return OK; -} - -Error MultiplayerReplicator::_encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) { - r_len = 0; - int size = 0; - - // Try raw encoding optimization. - if (r_raw && p_variants.size() == 1) { - *r_raw = false; - const Variant v = p_variants[0]; - if (v.get_type() == Variant::PACKED_BYTE_ARRAY) { - *r_raw = true; - const PackedByteArray pba = v; - if (p_buffer) { - memcpy(p_buffer, pba.ptr(), pba.size()); - } - r_len += pba.size(); - } else { - multiplayer->encode_and_compress_variant(v, p_buffer, size); - r_len += size; - } - return OK; - } - - // Regular encoding. - for (const Variant &v : p_variants) { - multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size); - r_len += size; - } - return OK; -} - -Error MultiplayerReplicator::_decode_state(const List<StringName> &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) { - r_len = 0; - int argc = p_properties.size(); - if (argc == 0 && p_raw) { - ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); - return OK; - } - ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA); - if (p_raw) { - r_len = p_len; - PackedByteArray pba; - pba.resize(p_len); - memcpy(pba.ptrw(), p_buffer, p_len); - p_obj->set(p_properties[0], pba); - return OK; - } - - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - - for (int i = 0; i < argc; i++) { - ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small."); - - int vlen; - Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen); - ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable."); - r_len += vlen; - } - ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes."); - - int i = 0; - for (const StringName &prop : p_properties) { - p_obj->set(prop, args[i]); - i += 1; - } - return OK; -} - -Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { - ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); -#ifdef TOOLS_ENABLED - if (!p_on_send.is_valid()) { - // We allow non scene spawning with custom callables. - String path = ResourceUID::get_singleton()->get_id_path(p_id); - RES res = ResourceLoader::load(path); - ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER); - } -#endif - if (p_mode == REPLICATION_MODE_NONE) { - if (replications.has(p_id)) { - replications.erase(p_id); - } - } else { - SceneConfig cfg; - cfg.mode = p_mode; - for (int i = 0; i < p_props.size(); i++) { - cfg.properties.push_back(StringName(p_props[i])); - } - cfg.on_spawn_despawn_send = p_on_send; - cfg.on_spawn_despawn_receive = p_on_recv; - replications[p_id] = cfg; - } - return OK; -} - -Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) { - ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty"); - ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED); - SceneConfig &cfg = replications[p_id]; - ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified"); - for (int i = 0; i < p_props.size(); i++) { - cfg.sync_properties.push_back(p_props[i]); - } - cfg.on_sync_send = p_on_send; - cfg.on_sync_receive = p_on_recv; - cfg.sync_interval = p_interval * 1000; - return OK; -} - -Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) { - int data_size = 0; - int is_raw = false; - if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { - const PackedByteArray pba = p_data; - is_raw = true; - data_size = p_data.operator PackedByteArray().size(); - } else if (p_data.get_type() == Variant::NIL) { - is_raw = true; - } else { - Error err = encode_variant(p_data, nullptr, data_size); - ERR_FAIL_COND_V(err, err); - } - MAKE_ROOM(SPAWN_CMD_OFFSET + data_size); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT); - encode_uint64(p_scene_id, &ptr[1]); - if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) { - const PackedByteArray pba = p_data; - memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size()); - } else if (data_size) { - encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size); - } - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(0); - network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return network_peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size); -} - -Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true); - } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); - NodePath path = p_path; - Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; - if (path.is_empty() && obj) { - Node *node = Object::cast_to<Node>(obj); - if (node && node->is_inside_tree()) { - path = node->get_path(); - } - } - ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree"); - return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false); - } -} - -Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false); - } else { - ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests."); - NodePath path = p_path; - Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr; - ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object."); - if (path.is_empty()) { - Node *node = Object::cast_to<Node>(obj); - if (node && node->is_inside_tree()) { - path = node->get_path(); - } - } - ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree"); - return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true); - } -} - -Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) { - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_spawn_despawn_send.is_valid()) { - Variant args[4]; - args[0] = p_peer; - args[1] = p_scene_id; - args[2] = p_obj; - args[3] = true; - const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] }; - Callable::CallError ce; - Variant ret; - cfg.on_spawn_despawn_send.call(argp, 4, ret, ce); - ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed"); - return OK; - } else { - Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation"); - return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn); - } -} - -Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { - return _spawn_despawn(p_scene_id, p_obj, p_peer, true); -} - -Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) { - return _spawn_despawn(p_scene_id, p_obj, p_peer, false); -} - -PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) { - PackedByteArray state; - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - int len = 0; - List<Variant> state_vars; - const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; - Error err = _get_state(props, p_obj, state_vars); - ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state."); - err = _encode_state(state_vars, nullptr, len); - ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state."); - state.resize(len); - _encode_state(state_vars, state.ptrw(), len); - return state; -} - -Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) { - ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties; - int size; - return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size); -} - -void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) { - if (!multiplayer->has_network_peer()) { - return; - } - Node *root_node = multiplayer->get_root_node(); - ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node); - NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path()); - if (path.is_empty()) { - return; - } - ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene); - if (!replications.has(id)) { - return; - } - const SceneConfig &cfg = replications[id]; - if (p_enter) { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) { - replicated_nodes[p_node->get_instance_id()] = id; - _track(id, p_node); - spawn(id, p_node, 0); - } - emit_signal(SNAME("replicated_instance_added"), id, p_node); - } else { - if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) { - replicated_nodes.erase(p_node->get_instance_id()); - _untrack(id, p_node); - despawn(id, p_node, 0); - } - emit_signal(SNAME("replicated_instance_removed"), id, p_node); - } -} - -void MultiplayerReplicator::spawn_all(int p_peer) { - for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) { - // Only server mode adds to replicated_nodes, no need to check it. - Object *obj = ObjectDB::get_instance(E.key); - ERR_CONTINUE(!obj); - Node *node = Object::cast_to<Node>(obj); - ERR_CONTINUE(!node); - spawn(E.value, node, p_peer); - } -} - -void MultiplayerReplicator::poll() { - for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) { - if (!E.value.sync_interval) { - continue; - } - if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) { - continue; - } - uint64_t time = OS::get_singleton()->get_ticks_usec(); - if (E.value.sync_last + E.value.sync_interval <= time) { - sync_all(E.key, 0); - E.value.sync_last = time; - } - // Handle wrapping. - if (E.value.sync_last > time) { - E.value.sync_last = time; - } - } -} - -void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!replications.has(p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); - _track(p_scene_id, p_obj); -} - -void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!p_obj); - ERR_FAIL_COND(!replications.has(p_scene_id)); - if (!tracked_objects.has(p_scene_id)) { - tracked_objects[p_scene_id] = List<ObjectID>(); - } - tracked_objects[p_scene_id].push_back(p_obj->get_instance_id()); -} - -void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!replications.has(p_scene_id)); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode."); - _untrack(p_scene_id, p_obj); -} - -void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) { - ERR_FAIL_COND(!p_obj); - ERR_FAIL_COND(!replications.has(p_scene_id)); - if (tracked_objects.has(p_scene_id)) { - tracked_objects[p_scene_id].erase(p_obj->get_instance_id()); - } -} - -Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) { - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - if (!tracked_objects.has(p_scene_id)) { - return OK; - } - const SceneConfig &cfg = replications[p_scene_id]; - if (cfg.on_sync_send.is_valid()) { - Array objs; - if (tracked_objects.has(p_scene_id)) { - objs.resize(tracked_objects[p_scene_id].size()); - int idx = 0; - for (const ObjectID &obj_id : tracked_objects[p_scene_id]) { - objs[idx++] = ObjectDB::get_instance(obj_id); - } - } - Variant args[3] = { p_scene_id, objs, p_peer }; - Variant *argp[3] = { args, &args[1], &args[2] }; - Callable::CallError ce; - Variant ret; - cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce); - ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed"); - return OK; - } else if (cfg.sync_properties.size()) { - return _sync_all_default(p_scene_id, p_peer); - } - return OK; -} - -Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) { - ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED); - ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER); - const SceneConfig &cfg = replications[p_scene_id]; - ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions"); - MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size()); - uint8_t *ptr = packet_cache.ptrw(); - ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC; - encode_uint64(p_scene_id, &ptr[1]); - Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer(); - network_peer->set_target_peer(p_peer_id); - network_peer->set_transfer_channel(p_channel); - network_peer->set_transfer_mode(p_transfer_mode); - return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size()); -} - -void MultiplayerReplicator::clear() { - tracked_objects.clear(); - replicated_nodes.clear(); -} - -void MultiplayerReplicator::_bind_methods() { - ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); - ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable())); - ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath())); - ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track); - ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack); - ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true)); - - ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); - - BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE); - BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER); - BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM); -} diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h deleted file mode 100644 index 2630ad7a8a..0000000000 --- a/core/io/multiplayer_replicator.h +++ /dev/null @@ -1,129 +0,0 @@ -/*************************************************************************/ -/* multiplayer_replicator.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MULTIPLAYER_REPLICATOR_H -#define MULTIPLAYER_REPLICATOR_H - -#include "core/io/multiplayer_api.h" - -#include "core/templates/hash_map.h" -#include "core/variant/typed_array.h" - -class MultiplayerReplicator : public Object { - GDCLASS(MultiplayerReplicator, Object); - -public: - enum { - SPAWN_CMD_OFFSET = 9, - SYNC_CMD_OFFSET = 9, - }; - - enum ReplicationMode { - REPLICATION_MODE_NONE, - REPLICATION_MODE_SERVER, - REPLICATION_MODE_CUSTOM, - }; - - struct SceneConfig { - ReplicationMode mode; - uint64_t sync_interval = 0; - uint64_t sync_last = 0; - uint8_t sync_recv = 0; - List<StringName> properties; - List<StringName> sync_properties; - Callable on_spawn_despawn_send; - Callable on_spawn_despawn_receive; - Callable on_sync_send; - Callable on_sync_receive; - }; - -protected: - static void _bind_methods(); - -private: - MultiplayerAPI *multiplayer = nullptr; - Vector<uint8_t> packet_cache; - Map<ResourceUID::ID, SceneConfig> replications; - Map<ObjectID, ResourceUID::ID> replicated_nodes; - HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects; - - // Encoding - Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant); - Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr); - Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false); - - // Spawn - Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn); - Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn); - void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn); - Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn); - - // Sync - void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len); - Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer); - void _track(const ResourceUID::ID &p_scene_id, Object *p_object); - void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object); - -public: - void clear(); - - // Encoding - PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial); - Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial); - - // Spawn - Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); - Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0); - Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath()); - - // Sync - Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable()); - Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer); - Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel); - void track(const ResourceUID::ID &p_scene_id, Object *p_object); - void untrack(const ResourceUID::ID &p_scene_id, Object *p_object); - - // Used by MultiplayerAPI - void spawn_all(int p_peer); - void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn); - void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len); - void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter); - void poll(); - - MultiplayerReplicator(MultiplayerAPI *p_multiplayer) { - multiplayer = p_multiplayer; - } -}; - -VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode); - -#endif // MULTIPLAYER_REPLICATOR_H diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp index 4a76f0191d..d34b5b6fe3 100644 --- a/core/io/packed_data_container.cpp +++ b/core/io/packed_data_container.cpp @@ -100,6 +100,7 @@ Variant PackedDataContainer::_iter_get_ofs(const Variant &p_iter, uint32_t p_off } Variant PackedDataContainer::_get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, bool &err) const { + ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), Variant()); uint32_t type = decode_uint32(p_buf + p_ofs); if (type == TYPE_ARRAY || type == TYPE_DICT) { @@ -122,6 +123,7 @@ Variant PackedDataContainer::_get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, b } uint32_t PackedDataContainer::_type_at_ofs(uint32_t p_ofs) const { + ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), 0); const uint8_t *rd = data.ptr(); ERR_FAIL_COND_V(!rd, 0); const uint8_t *r = &rd[p_ofs]; @@ -131,6 +133,7 @@ uint32_t PackedDataContainer::_type_at_ofs(uint32_t p_ofs) const { } int PackedDataContainer::_size(uint32_t p_ofs) const { + ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), 0); const uint8_t *rd = data.ptr(); ERR_FAIL_COND_V(!rd, 0); const uint8_t *r = &rd[p_ofs]; @@ -149,6 +152,7 @@ int PackedDataContainer::_size(uint32_t p_ofs) const { } Variant PackedDataContainer::_key_at_ofs(uint32_t p_ofs, const Variant &p_key, bool &err) const { + ERR_FAIL_COND_V(p_ofs + 4 > (uint32_t)data.size(), Variant()); const uint8_t *rd = data.ptr(); if (!rd) { err = true; diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index 8da44fd290..87d2b66e5b 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -138,6 +138,7 @@ Error PacketPeer::_get_packet_error() const { void PacketPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_var", "allow_objects"), &PacketPeer::_bnd_get_var, DEFVAL(false)); ClassDB::bind_method(D_METHOD("put_var", "var", "full_objects"), &PacketPeer::put_var, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_packet"), &PacketPeer::_get_packet); ClassDB::bind_method(D_METHOD("put_packet", "buffer"), &PacketPeer::_put_packet); ClassDB::bind_method(D_METHOD("get_packet_error"), &PacketPeer::_get_packet_error); @@ -151,6 +152,51 @@ void PacketPeer::_bind_methods() { /***************/ +int PacketPeerExtension::get_available_packet_count() const { + int count; + if (GDVIRTUAL_CALL(_get_available_packet_count, count)) { + return count; + } + WARN_PRINT_ONCE("PacketPeerExtension::_get_available_packet_count is unimplemented!"); + return -1; +} + +Error PacketPeerExtension::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + int err; + if (GDVIRTUAL_CALL(_get_packet, r_buffer, &r_buffer_size, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("PacketPeerExtension::_get_packet_native is unimplemented!"); + return FAILED; +} + +Error PacketPeerExtension::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + int err; + if (GDVIRTUAL_CALL(_put_packet, p_buffer, p_buffer_size, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("PacketPeerExtension::_put_packet_native is unimplemented!"); + return FAILED; +} + +int PacketPeerExtension::get_max_packet_size() const { + int size; + if (GDVIRTUAL_CALL(_get_max_packet_size, size)) { + return size; + } + WARN_PRINT_ONCE("PacketPeerExtension::_get_max_packet_size is unimplemented!"); + return 0; +} + +void PacketPeerExtension::_bind_methods() { + GDVIRTUAL_BIND(_get_packet, "r_buffer", "r_buffer_size"); + GDVIRTUAL_BIND(_put_packet, "p_buffer", "p_buffer_size"); + GDVIRTUAL_BIND(_get_available_packet_count); + GDVIRTUAL_BIND(_get_max_packet_size); +} + +/***************/ + void PacketPeerStream::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream_peer", "peer"), &PacketPeerStream::set_stream_peer); ClassDB::bind_method(D_METHOD("get_stream_peer"), &PacketPeerStream::get_stream_peer); diff --git a/core/io/packet_peer.h b/core/io/packet_peer.h index 9a345af3d0..bc1f4aaabf 100644 --- a/core/io/packet_peer.h +++ b/core/io/packet_peer.h @@ -35,6 +35,10 @@ #include "core/object/class_db.h" #include "core/templates/ring_buffer.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" +#include "core/variant/native_ptr.h" + class PacketPeer : public RefCounted { GDCLASS(PacketPeer, RefCounted); @@ -73,6 +77,25 @@ public: ~PacketPeer() {} }; +class PacketPeerExtension : public PacketPeer { + GDCLASS(PacketPeerExtension, PacketPeer); + +protected: + static void _bind_methods(); + +public: + virtual int get_available_packet_count() const override; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; + virtual int get_max_packet_size() const override; + + /* GDExtension */ + GDVIRTUAL0RC(int, _get_available_packet_count); + GDVIRTUAL2R(int, _get_packet, GDNativeConstPtr<const uint8_t *>, GDNativePtr<int>); + GDVIRTUAL2R(int, _put_packet, GDNativeConstPtr<const uint8_t>, int); + GDVIRTUAL0RC(int, _get_max_packet_size); +}; + class PacketPeerStream : public PacketPeer { GDCLASS(PacketPeerStream, PacketPeer); diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index 806a95398f..8d75581342 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -47,13 +47,14 @@ static int _get_pad(int p_alignment, int p_n) { } void PCKPacker::_bind_methods() { - ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(0), DEFVAL(String()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false)); ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false)); } Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) { ERR_FAIL_COND_V_MSG((p_key.is_empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long)."); + ERR_FAIL_COND_V_MSG(p_alignment <= 0, ERR_CANT_CREATE, "Invalid alignment, must be greater then 0."); String _key = p_key.to_lower(); key.resize(32); diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h index 3d2ce8f240..bd8902a01d 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -58,7 +58,7 @@ class PCKPacker : public RefCounted { Vector<File> files; public: - Error pck_start(const String &p_file, int p_alignment = 0, const String &p_key = String(), bool p_encrypt_directory = false); + Error pck_start(const String &p_file, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false); Error flush(bool p_verbose = false); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 87b4d7195d..972076e397 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -136,6 +136,7 @@ String Resource::get_scene_unique_id() const { void Resource::set_name(const String &p_name) { name = p_name; + emit_changed(); } String Resource::get_name() const { @@ -540,9 +541,9 @@ void ResourceCache::dump(const char *p_file, bool p_short) { } } - for (Map<String, int>::Element *E = type_count.front(); E; E = E->next()) { + for (const KeyValue<String, int> &E : type_count) { if (f) { - f->store_line(E->key() + " count: " + itos(E->get())); + f->store_line(E.key + " count: " + itos(E.value)); } } if (f) { diff --git a/core/io/resource.h b/core/io/resource.h index 9ccc247887..109c0f6611 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -103,6 +103,7 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; + _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.find("::") != -1 || path_cache.begins_with("local://"); } static String generate_scene_unique_id(); void set_scene_unique_id(const String &p_id); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 84fd6496a7..a5a195f859 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1572,7 +1572,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia return; // don't save it } - if (res->get_path().length() && res->get_path().find("::") == -1) { + if (!res->is_built_in()) { f->store_32(OBJECT_EXTERNAL_RESOURCE_INDEX); f->store_32(external_resources[res]); } else { @@ -1743,7 +1743,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant return; } - if (!p_main && (!bundle_resources) && res->get_path().length() && res->get_path().find("::") == -1) { + if (!p_main && (!bundle_resources) && !res->is_built_in()) { if (res->get_path() == path) { ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); return; @@ -1960,8 +1960,8 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p Vector<RES> save_order; save_order.resize(external_resources.size()); - for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { - save_order.write[E->get()] = E->key(); + for (const KeyValue<RES, int> &E : external_resources) { + save_order.write[E.value] = E.key; } for (int i = 0; i < save_order.size(); i++) { @@ -1978,7 +1978,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p Set<String> used_unique_ids; for (RES &r : saved_resources) { - if (r->get_path() == "" || r->get_path().find("::") != -1) { + if (r->is_built_in()) { if (r->get_scene_unique_id() != "") { if (used_unique_ids.has(r->get_scene_unique_id())) { r->set_scene_unique_id(""); @@ -1992,7 +1992,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p Map<RES, int> resource_map; int res_index = 0; for (RES &r : saved_resources) { - if (r->get_path() == "" || r->get_path().find("::") != -1) { + if (r->is_built_in()) { if (r->get_scene_unique_id() == "") { String new_id; diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index 1e166015b0..cd44c537a8 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -418,7 +418,7 @@ Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const St } String ResourceFormatImporter::get_import_base_path(const String &p_for_file) const { - return ProjectSettings::IMPORTED_FILES_PATH.plus_file(p_for_file.get_file() + "-" + p_for_file.md5_text()); + return ProjectSettings::get_singleton()->get_imported_files_path().plus_file(p_for_file.get_file() + "-" + p_for_file.md5_text()); } bool ResourceFormatImporter::are_import_settings_valid(const String &p_path) const { diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index a1cacbd306..cd583e2533 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -134,8 +134,8 @@ public: virtual int get_preset_count() const { return 0; } virtual String get_preset_name(int p_idx) const { return String(); } - virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const = 0; - virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const = 0; + virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const = 0; + virtual bool get_option_visibility(const String &p_path, const String &p_option, const Map<StringName, Variant> &p_options) const = 0; virtual String get_option_group_file() const { return String(); } virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 3026236f07..2198761c2a 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -156,8 +156,8 @@ void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> * Error ResourceFormatLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { Dictionary deps_dict; - for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { - deps_dict[E->key()] = E->value(); + for (KeyValue<String, String> E : p_map) { + deps_dict[E.key] = E.value; } int64_t err; diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 97d683f415..b7d01712ff 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "resource_uid.h" + +#include "core/config/project_settings.h" #include "core/crypto/crypto.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" @@ -36,7 +38,9 @@ static constexpr uint32_t char_count = ('z' - 'a'); static constexpr uint32_t base = char_count + ('9' - '0'); -const char *ResourceUID::CACHE_FILE = "res://.godot/uid_cache.bin"; +String ResourceUID::get_cache_file() { + return ProjectSettings::get_singleton()->get_project_data_path().plus_file("uid_cache.bin"); +} String ResourceUID::id_to_text(ID p_id) const { if (p_id < 0) { @@ -126,8 +130,7 @@ String ResourceUID::get_id_path(ID p_id) const { MutexLock l(mutex); ERR_FAIL_COND_V(!unique_ids.has(p_id), String()); const CharString &cs = unique_ids[p_id].cs; - String s(cs.ptr()); - return s; + return String::utf8(cs.ptr()); } void ResourceUID::remove_id(ID p_id) { MutexLock l(mutex); @@ -136,12 +139,13 @@ void ResourceUID::remove_id(ID p_id) { } Error ResourceUID::save_to_cache() { - if (!FileAccess::exists(CACHE_FILE)) { + String cache_file = get_cache_file(); + if (!FileAccess::exists(cache_file)) { DirAccessRef d = DirAccess::create(DirAccess::ACCESS_RESOURCES); - d->make_dir_recursive(String(CACHE_FILE).get_base_dir()); //ensure base dir exists + d->make_dir_recursive(String(cache_file).get_base_dir()); //ensure base dir exists } - FileAccessRef f = FileAccess::open(CACHE_FILE, FileAccess::WRITE); + FileAccessRef f = FileAccess::open(cache_file, FileAccess::WRITE); if (!f) { return ERR_CANT_OPEN; } @@ -165,7 +169,7 @@ Error ResourceUID::save_to_cache() { } Error ResourceUID::load_from_cache() { - FileAccessRef f = FileAccess::open(CACHE_FILE, FileAccess::READ); + FileAccessRef f = FileAccess::open(get_cache_file(), FileAccess::READ); if (!f) { return ERR_CANT_OPEN; } @@ -207,7 +211,7 @@ Error ResourceUID::update_cache() { for (OrderedHashMap<ID, Cache>::Element E = unique_ids.front(); E; E = E.next()) { if (!E.get().saved_to_cache) { if (f == nullptr) { - f = FileAccess::open(CACHE_FILE, FileAccess::READ_WRITE); //append + f = FileAccess::open(get_cache_file(), FileAccess::READ_WRITE); //append if (!f) { return ERR_CANT_OPEN; } diff --git a/core/io/resource_uid.h b/core/io/resource_uid.h index b12138425a..2f1bfdf243 100644 --- a/core/io/resource_uid.h +++ b/core/io/resource_uid.h @@ -44,7 +44,7 @@ public: INVALID_ID = -1 }; - static const char *CACHE_FILE; + static String get_cache_file(); private: mutable Ref<Crypto> crypto; diff --git a/core/io/stream_peer.cpp b/core/io/stream_peer.cpp index 27f8d4e88f..8ab025dda1 100644 --- a/core/io/stream_peer.cpp +++ b/core/io/stream_peer.cpp @@ -410,6 +410,63 @@ void StreamPeer::_bind_methods() { //////////////////////////////// +int StreamPeerExtension::get_available_bytes() const { + int count; + if (GDVIRTUAL_CALL(_get_available_bytes, count)) { + return count; + } + WARN_PRINT_ONCE("StreamPeerExtension::_get_available_bytes is unimplemented!"); + return -1; +} + +Error StreamPeerExtension::get_data(uint8_t *r_buffer, int p_bytes) { + int err; + int received = 0; + if (GDVIRTUAL_CALL(_get_data, r_buffer, p_bytes, &received, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("StreamPeerExtension::_get_data is unimplemented!"); + return FAILED; +} + +Error StreamPeerExtension::get_partial_data(uint8_t *r_buffer, int p_bytes, int &r_received) { + int err; + if (GDVIRTUAL_CALL(_get_partial_data, r_buffer, p_bytes, &r_received, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("StreamPeerExtension::_get_partial_data is unimplemented!"); + return FAILED; +} + +Error StreamPeerExtension::put_data(const uint8_t *p_data, int p_bytes) { + int err; + int sent = 0; + if (GDVIRTUAL_CALL(_put_data, p_data, p_bytes, &sent, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("StreamPeerExtension::_put_data is unimplemented!"); + return FAILED; +} + +Error StreamPeerExtension::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { + int err; + if (GDVIRTUAL_CALL(_put_data, p_data, p_bytes, &r_sent, err)) { + return (Error)err; + } + WARN_PRINT_ONCE("StreamPeerExtension::_put_partial_data is unimplemented!"); + return FAILED; +} + +void StreamPeerExtension::_bind_methods() { + GDVIRTUAL_BIND(_get_data, "r_buffer", "r_bytes", "r_received"); + GDVIRTUAL_BIND(_get_partial_data, "r_buffer", "r_bytes", "r_received"); + GDVIRTUAL_BIND(_put_data, "p_data", "p_bytes", "r_sent"); + GDVIRTUAL_BIND(_put_partial_data, "p_data", "p_bytes", "r_sent"); + GDVIRTUAL_BIND(_get_available_bytes); +} + +//////////////////////////////// + void StreamPeerBuffer::_bind_methods() { ClassDB::bind_method(D_METHOD("seek", "position"), &StreamPeerBuffer::seek); ClassDB::bind_method(D_METHOD("get_size"), &StreamPeerBuffer::get_size); diff --git a/core/io/stream_peer.h b/core/io/stream_peer.h index effc3850af..89432951c5 100644 --- a/core/io/stream_peer.h +++ b/core/io/stream_peer.h @@ -33,6 +33,10 @@ #include "core/object/ref_counted.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" +#include "core/variant/native_ptr.h" + class StreamPeer : public RefCounted { GDCLASS(StreamPeer, RefCounted); OBJ_CATEGORY("Networking"); @@ -58,6 +62,7 @@ public: virtual int get_available_bytes() const = 0; + /* helpers */ void set_big_endian(bool p_big_endian); bool is_big_endian_enabled() const; @@ -92,6 +97,26 @@ public: StreamPeer() {} }; +class StreamPeerExtension : public StreamPeer { + GDCLASS(StreamPeerExtension, StreamPeer); + +protected: + static void _bind_methods(); + +public: + virtual Error put_data(const uint8_t *p_data, int p_bytes) override; + virtual Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) override; + virtual Error get_data(uint8_t *p_buffer, int p_bytes) override; + virtual Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) override; + virtual int get_available_bytes() const override; + + GDVIRTUAL3R(int, _put_data, GDNativeConstPtr<const uint8_t>, int, GDNativePtr<int>); + GDVIRTUAL3R(int, _put_partial_data, GDNativeConstPtr<const uint8_t>, int, GDNativePtr<int>); + GDVIRTUAL3R(int, _get_data, GDNativePtr<uint8_t>, int, GDNativePtr<int>); + GDVIRTUAL3R(int, _get_partial_data, GDNativePtr<uint8_t>, int, GDNativePtr<int>); + GDVIRTUAL0RC(int, _get_available_bytes); +}; + class StreamPeerBuffer : public StreamPeer { GDCLASS(StreamPeerBuffer, StreamPeer); diff --git a/core/io/zip_io.cpp b/core/io/zip_io.cpp index fb4c76aa7a..24808cc8d6 100644 --- a/core/io/zip_io.cpp +++ b/core/io/zip_io.cpp @@ -100,7 +100,7 @@ int zipio_testerror(voidpf opaque, voidpf stream) { } voidpf zipio_alloc(voidpf opaque, uInt items, uInt size) { - voidpf ptr = memalloc(items * size); + voidpf ptr = memalloc((size_t)items * size); memset(ptr, 0, items * size); return ptr; } |
