diff options
Diffstat (limited to 'core/io')
88 files changed, 11155 insertions, 2858 deletions
diff --git a/core/io/compression.cpp b/core/io/compression.cpp index 7480262835..790b6febc0 100644 --- a/core/io/compression.cpp +++ b/core/io/compression.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,8 @@ #include "compression.h" +#include "core/config/project_settings.h" #include "core/io/zip_io.h" -#include "core/os/copymem.h" -#include "core/project_settings.h" #include "thirdparty/misc/fastlz.h" @@ -44,8 +43,8 @@ int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, case MODE_FASTLZ: { if (p_src_size < 16) { uint8_t src[16]; - zeromem(&src[p_src_size], 16 - p_src_size); - copymem(src, p_src, p_src_size); + memset(&src[p_src_size], 0, 16 - p_src_size); + memcpy(src, p_src, p_src_size); return fastlz_compress(src, 16, p_dst); } else { return fastlz_compress(p_src, p_src_size, p_dst); @@ -135,8 +134,9 @@ int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p if (p_dst_max_size < 16) { uint8_t dst[16]; - ret_size = fastlz_decompress(p_src, p_src_size, dst, 16); - copymem(p_dst, dst, p_dst_max_size); + fastlz_decompress(p_src, p_src_size, dst, 16); + memcpy(p_dst, dst, p_dst_max_size); + ret_size = p_dst_max_size; } else { ret_size = fastlz_decompress(p_src, p_src_size, p_dst, p_dst_max_size); } @@ -181,8 +181,8 @@ int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p } /** - This will handle both Gzip and Deflat streams. It will automatically allocate the output buffer into the provided p_dst_vect Vector. - This is required for compressed data who's final uncompressed size is unknown, as is the case for HTTP response bodies. + This will handle both Gzip and Deflate streams. It will automatically allocate the output buffer into the provided p_dst_vect Vector. + This is required for compressed data whose final uncompressed size is unknown, as is the case for HTTP response bodies. This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer. */ int Compression::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) { @@ -239,7 +239,10 @@ int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_s case Z_DATA_ERROR: case Z_MEM_ERROR: case Z_STREAM_ERROR: - WARN_PRINT(strm.msg); + case Z_BUF_ERROR: + if (strm.msg) { + WARN_PRINT(strm.msg); + } (void)inflateEnd(&strm); p_dst_vect->resize(0); return ret; @@ -248,7 +251,7 @@ int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_s out_mark += gzip_chunk; - // Encorce max output size + // Enforce max output size if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) { (void)inflateEnd(&strm); p_dst_vect->resize(0); @@ -257,13 +260,13 @@ int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_s } while (ret != Z_STREAM_END); // If all done successfully, resize the output if it's larger than the actual output - if (ret == Z_STREAM_END && (unsigned long)p_dst_vect->size() > strm.total_out) { + if ((unsigned long)p_dst_vect->size() > strm.total_out) { p_dst_vect->resize(strm.total_out); } // clean up and return (void)inflateEnd(&strm); - return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; + return Z_OK; } int Compression::zlib_level = Z_DEFAULT_COMPRESSION; diff --git a/core/io/compression.h b/core/io/compression.h index c103fa8eae..cbfed74124 100644 --- a/core/io/compression.h +++ b/core/io/compression.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,8 +31,8 @@ #ifndef COMPRESSION_H #define COMPRESSION_H +#include "core/templates/vector.h" #include "core/typedefs.h" -#include "core/vector.h" class Compression { public: diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index 1af9142317..49fa73dab2 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,7 @@ #include "core/io/file_access_encrypted.h" #include "core/os/keyboard.h" -#include "core/variant_parser.h" +#include "core/variant/variant_parser.h" PackedStringArray ConfigFile::_get_sections() const { List<String> s; @@ -40,8 +40,8 @@ PackedStringArray ConfigFile::_get_sections() const { PackedStringArray arr; arr.resize(s.size()); int idx = 0; - for (const List<String>::Element *E = s.front(); E; E = E->next()) { - arr.set(idx++, E->get()); + for (const String &E : s) { + arr.set(idx++, E); } return arr; @@ -53,8 +53,8 @@ PackedStringArray ConfigFile::_get_section_keys(const String &p_section) const { PackedStringArray arr; arr.resize(s.size()); int idx = 0; - for (const List<String>::Element *E = s.front(); E; E = E->next()) { - arr.set(idx++, E->get()); + for (const String &E : s) { + arr.set(idx++, E); } return arr; @@ -67,7 +67,7 @@ void ConfigFile::set_value(const String &p_section, const String &p_key, const V return; // ? } values[p_section].erase(p_key); - if (values[p_section].empty()) { + if (values[p_section].is_empty()) { values.erase(p_section); } @@ -188,7 +188,7 @@ Error ConfigFile::_internal_save(FileAccess *file) { for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) { String vstr; VariantWriter::write_to_string(F.get(), vstr); - file->store_string(F.key() + "=" + vstr + "\n"); + file->store_string(F.key().property_name_encode() + "=" + vstr + "\n"); } } @@ -295,6 +295,9 @@ Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream) return OK; } +void ConfigFile::clear() { + values.clear(); +} void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value); ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant())); @@ -312,9 +315,13 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); + ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass); ClassDB::bind_method(D_METHOD("save_encrypted", "path", "key"), &ConfigFile::save_encrypted); ClassDB::bind_method(D_METHOD("save_encrypted_pass", "path", "password"), &ConfigFile::save_encrypted_pass); + + ClassDB::bind_method(D_METHOD("clear"), &ConfigFile::clear); } diff --git a/core/io/config_file.h b/core/io/config_file.h index ae06960f02..dbba43ace5 100644 --- a/core/io/config_file.h +++ b/core/io/config_file.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,13 +31,13 @@ #ifndef CONFIG_FILE_H #define CONFIG_FILE_H -#include "core/ordered_hash_map.h" -#include "core/os/file_access.h" -#include "core/reference.h" -#include "core/variant_parser.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" +#include "core/templates/ordered_hash_map.h" +#include "core/variant/variant_parser.h" -class ConfigFile : public Reference { - GDCLASS(ConfigFile, Reference); +class ConfigFile : public RefCounted { + GDCLASS(ConfigFile, RefCounted); OrderedHashMap<String, OrderedHashMap<String, Variant>> values; @@ -68,6 +68,8 @@ public: Error load(const String &p_path); Error parse(const String &p_data); + void clear(); + Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key); Error load_encrypted_pass(const String &p_path, const String &p_pass); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp new file mode 100644 index 0000000000..8234adea06 --- /dev/null +++ b/core/io/dir_access.cpp @@ -0,0 +1,417 @@ +/*************************************************************************/ +/* dir_access.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 "dir_access.h" + +#include "core/config/project_settings.h" +#include "core/io/file_access.h" +#include "core/os/memory.h" +#include "core/os/os.h" + +String DirAccess::_get_root_path() const { + switch (_access_type) { + case ACCESS_RESOURCES: + return ProjectSettings::get_singleton()->get_resource_path(); + case ACCESS_USERDATA: + return OS::get_singleton()->get_user_data_dir(); + default: + return ""; + } +} + +String DirAccess::_get_root_string() const { + switch (_access_type) { + case ACCESS_RESOURCES: + return "res://"; + case ACCESS_USERDATA: + return "user://"; + default: + return ""; + } +} + +int DirAccess::get_current_drive() { + String path = get_current_dir().to_lower(); + for (int i = 0; i < get_drive_count(); i++) { + String d = get_drive(i).to_lower(); + if (path.begins_with(d)) { + return i; + } + } + + return 0; +} + +bool DirAccess::drives_are_shortcuts() { + return false; +} + +static Error _erase_recursive(DirAccess *da) { + List<String> dirs; + List<String> files; + + da->list_dir_begin(); + String n = da->get_next(); + while (n != String()) { + if (n != "." && n != "..") { + if (da->current_is_dir()) { + dirs.push_back(n); + } else { + files.push_back(n); + } + } + + n = da->get_next(); + } + + da->list_dir_end(); + + for (const String &E : dirs) { + Error err = da->change_dir(E); + if (err == OK) { + err = _erase_recursive(da); + if (err) { + da->change_dir(".."); + return err; + } + err = da->change_dir(".."); + if (err) { + return err; + } + err = da->remove(da->get_current_dir().plus_file(E)); + if (err) { + return err; + } + } else { + return err; + } + } + + for (const String &E : files) { + Error err = da->remove(da->get_current_dir().plus_file(E)); + if (err) { + return err; + } + } + + return OK; +} + +Error DirAccess::erase_contents_recursive() { + return _erase_recursive(this); +} + +Error DirAccess::make_dir_recursive(String p_dir) { + if (p_dir.length() < 1) { + return OK; + } + + String full_dir; + + if (p_dir.is_rel_path()) { + //append current + full_dir = get_current_dir().plus_file(p_dir); + + } else { + full_dir = p_dir; + } + + full_dir = full_dir.replace("\\", "/"); + + //int slices = full_dir.get_slice_count("/"); + + String base; + + if (full_dir.begins_with("res://")) { + base = "res://"; + } else if (full_dir.begins_with("user://")) { + base = "user://"; + } else if (full_dir.begins_with("/")) { + base = "/"; + } else if (full_dir.find(":/") != -1) { + base = full_dir.substr(0, full_dir.find(":/") + 2); + } else { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + + full_dir = full_dir.replace_first(base, "").simplify_path(); + + Vector<String> subdirs = full_dir.split("/"); + + String curpath = base; + for (int i = 0; i < subdirs.size(); i++) { + curpath = curpath.plus_file(subdirs[i]); + Error err = make_dir(curpath); + if (err != OK && err != ERR_ALREADY_EXISTS) { + ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath); + } + } + + return OK; +} + +String DirAccess::fix_path(String p_path) const { + switch (_access_type) { + case ACCESS_RESOURCES: { + if (ProjectSettings::get_singleton()) { + if (p_path.begins_with("res://")) { + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + if (resource_path != "") { + return p_path.replace_first("res:/", resource_path); + } + return p_path.replace_first("res://", ""); + } + } + + } break; + case ACCESS_USERDATA: { + if (p_path.begins_with("user://")) { + String data_dir = OS::get_singleton()->get_user_data_dir(); + if (data_dir != "") { + return p_path.replace_first("user:/", data_dir); + } + return p_path.replace_first("user://", ""); + } + + } break; + case ACCESS_FILESYSTEM: { + return p_path; + } break; + case ACCESS_MAX: + break; // Can't happen, but silences warning + } + + return p_path; +} + +DirAccess::CreateFunc DirAccess::create_func[ACCESS_MAX] = { nullptr, nullptr, nullptr }; + +DirAccess *DirAccess::create_for_path(const String &p_path) { + DirAccess *da = nullptr; + if (p_path.begins_with("res://")) { + da = create(ACCESS_RESOURCES); + } else if (p_path.begins_with("user://")) { + da = create(ACCESS_USERDATA); + } else { + da = create(ACCESS_FILESYSTEM); + } + + return da; +} + +DirAccess *DirAccess::open(const String &p_path, Error *r_error) { + DirAccess *da = create_for_path(p_path); + + ERR_FAIL_COND_V_MSG(!da, nullptr, "Cannot create DirAccess for path '" + p_path + "'."); + Error err = da->change_dir(p_path); + if (r_error) { + *r_error = err; + } + if (err != OK) { + memdelete(da); + return nullptr; + } + + return da; +} + +DirAccess *DirAccess::create(AccessType p_access) { + DirAccess *da = create_func[p_access] ? create_func[p_access]() : nullptr; + if (da) { + da->_access_type = p_access; + } + + return da; +} + +String DirAccess::get_full_path(const String &p_path, AccessType p_access) { + DirAccess *d = DirAccess::create(p_access); + if (!d) { + return p_path; + } + + d->change_dir(p_path); + String full = d->get_current_dir(); + memdelete(d); + return full; +} + +Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) { + //printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data()); + Error err; + FileAccess *fsrc = FileAccess::open(p_from, FileAccess::READ, &err); + + if (err) { + ERR_PRINT("Failed to open " + p_from); + return err; + } + + FileAccess *fdst = FileAccess::open(p_to, FileAccess::WRITE, &err); + if (err) { + fsrc->close(); + memdelete(fsrc); + ERR_PRINT("Failed to open " + p_to); + return err; + } + + fsrc->seek_end(0); + int size = fsrc->get_position(); + fsrc->seek(0); + err = OK; + while (size--) { + if (fsrc->get_error() != OK) { + err = fsrc->get_error(); + break; + } + if (fdst->get_error() != OK) { + err = fdst->get_error(); + break; + } + + fdst->store_8(fsrc->get_8()); + } + + if (err == OK && p_chmod_flags != -1) { + fdst->close(); + err = FileAccess::set_unix_permissions(p_to, p_chmod_flags); + // If running on a platform with no chmod support (i.e., Windows), don't fail + if (err == ERR_UNAVAILABLE) { + err = OK; + } + } + + memdelete(fsrc); + memdelete(fdst); + + return err; +} + +// Changes dir for the current scope, returning back to the original dir +// when scope exits +class DirChanger { + DirAccess *da; + String original_dir; + +public: + DirChanger(DirAccess *p_da, String p_dir) : + da(p_da), + original_dir(p_da->get_current_dir()) { + p_da->change_dir(p_dir); + } + + ~DirChanger() { + da->change_dir(original_dir); + } +}; + +Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links) { + List<String> dirs; + + String curdir = get_current_dir(); + list_dir_begin(); + String n = get_next(); + while (n != String()) { + if (n != "." && n != "..") { + if (p_copy_links && is_link(get_current_dir().plus_file(n))) { + create_link(read_link(get_current_dir().plus_file(n)), p_to + n); + } else if (current_is_dir()) { + dirs.push_back(n); + } else { + const String &rel_path = n; + if (!n.is_rel_path()) { + list_dir_end(); + return ERR_BUG; + } + Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags); + if (err) { + list_dir_end(); + return err; + } + } + } + + n = get_next(); + } + + list_dir_end(); + + for (const String &rel_path : dirs) { + String target_dir = p_to + rel_path; + if (!p_target_da->dir_exists(target_dir)) { + Error err = p_target_da->make_dir(target_dir); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'."); + } + + Error err = change_dir(rel_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'."); + + err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links); + if (err) { + change_dir(".."); + ERR_FAIL_V_MSG(err, "Failed to copy recursively."); + } + err = change_dir(".."); + ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back."); + } + + return OK; +} + +Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags, bool p_copy_links) { + ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist."); + + DirAccess *target_da = DirAccess::create_for_path(p_to); + ERR_FAIL_COND_V_MSG(!target_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'."); + + if (!target_da->dir_exists(p_to)) { + Error err = target_da->make_dir_recursive(p_to); + if (err) { + memdelete(target_da); + } + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'."); + } + + if (!p_to.ends_with("/")) { + p_to = p_to + "/"; + } + + DirChanger dir_changer(this, p_from); + Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links); + memdelete(target_da); + + return err; +} + +bool DirAccess::exists(String p_dir) { + DirAccess *da = DirAccess::create_for_path(p_dir); + bool valid = da->change_dir(p_dir) == OK; + memdelete(da); + return valid; +} diff --git a/core/io/dir_access.h b/core/io/dir_access.h new file mode 100644 index 0000000000..16154a4850 --- /dev/null +++ b/core/io/dir_access.h @@ -0,0 +1,147 @@ +/*************************************************************************/ +/* dir_access.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 DIR_ACCESS_H +#define DIR_ACCESS_H + +#include "core/string/ustring.h" +#include "core/typedefs.h" + +//@ TODO, excellent candidate for THREAD_SAFE MACRO, should go through all these and add THREAD_SAFE where it applies +class DirAccess { +public: + enum AccessType { + ACCESS_RESOURCES, + ACCESS_USERDATA, + ACCESS_FILESYSTEM, + ACCESS_MAX + }; + + typedef DirAccess *(*CreateFunc)(); + +private: + AccessType _access_type = ACCESS_FILESYSTEM; + static CreateFunc create_func[ACCESS_MAX]; ///< set this to instance a filesystem object + + Error _copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links); + +protected: + String _get_root_path() const; + String _get_root_string() const; + + String fix_path(String p_path) const; + + template <class T> + static DirAccess *_create_builtin() { + return memnew(T); + } + +public: + virtual Error list_dir_begin() = 0; ///< This starts dir listing + virtual String get_next() = 0; + virtual bool current_is_dir() const = 0; + virtual bool current_is_hidden() const = 0; + + virtual void list_dir_end() = 0; ///< + + virtual int get_drive_count() = 0; + virtual String get_drive(int p_drive) = 0; + virtual int get_current_drive(); + virtual bool drives_are_shortcuts(); + + virtual Error change_dir(String p_dir) = 0; ///< can be relative or absolute, return false on success + virtual String get_current_dir(bool p_include_drive = true) = 0; ///< return current dir location + virtual Error make_dir(String p_dir) = 0; + virtual Error make_dir_recursive(String p_dir); + virtual Error erase_contents_recursive(); //super dangerous, use with care! + + virtual bool file_exists(String p_file) = 0; + virtual bool dir_exists(String p_dir) = 0; + virtual bool is_readable(String p_dir) { return true; }; + virtual bool is_writable(String p_dir) { return true; }; + static bool exists(String p_dir); + virtual uint64_t get_space_left() = 0; + + Error copy_dir(String p_from, String p_to, int p_chmod_flags = -1, bool p_copy_links = false); + virtual Error copy(String p_from, String p_to, int p_chmod_flags = -1); + virtual Error rename(String p_from, String p_to) = 0; + virtual Error remove(String p_name) = 0; + + virtual bool is_link(String p_file) = 0; + virtual String read_link(String p_file) = 0; + virtual Error create_link(String p_source, String p_target) = 0; + + // Meant for editor code when we want to quickly remove a file without custom + // handling (e.g. removing a cache file). + static void remove_file_or_error(String p_path) { + DirAccess *da = create(ACCESS_FILESYSTEM); + if (da->file_exists(p_path)) { + if (da->remove(p_path) != OK) { + ERR_FAIL_MSG("Cannot remove file or directory: " + p_path); + } + } + memdelete(da); + } + + virtual String get_filesystem_type() const = 0; + static String get_full_path(const String &p_path, AccessType p_access); + static DirAccess *create_for_path(const String &p_path); + + static DirAccess *create(AccessType p_access); + + template <class T> + static void make_default(AccessType p_access) { + create_func[p_access] = _create_builtin<T>; + } + + static DirAccess *open(const String &p_path, Error *r_error = nullptr); + + DirAccess() {} + virtual ~DirAccess() {} +}; + +struct DirAccessRef { + _FORCE_INLINE_ DirAccess *operator->() { + return f; + } + + operator bool() const { return f != nullptr; } + + DirAccess *f; + + DirAccessRef(DirAccess *fa) { f = fa; } + ~DirAccessRef() { + if (f) { + memdelete(f); + } + } +}; + +#endif // DIR_ACCESS_H diff --git a/core/io/dtls_server.cpp b/core/io/dtls_server.cpp index e43b1f5385..655fb18535 100644 --- a/core/io/dtls_server.cpp +++ b/core/io/dtls_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,8 +30,8 @@ #include "dtls_server.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" +#include "core/io/file_access.h" DTLSServer *(*DTLSServer::_create)() = nullptr; bool DTLSServer::available = false; diff --git a/core/io/dtls_server.h b/core/io/dtls_server.h index ae1d3bcd98..02a32533e1 100644 --- a/core/io/dtls_server.h +++ b/core/io/dtls_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,8 +34,8 @@ #include "core/io/net_socket.h" #include "core/io/packet_peer_dtls.h" -class DTLSServer : public Reference { - GDCLASS(DTLSServer, Reference); +class DTLSServer : public RefCounted { + GDCLASS(DTLSServer, RefCounted); protected: static DTLSServer *(*_create)(); diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp new file mode 100644 index 0000000000..e6e79dff8a --- /dev/null +++ b/core/io/file_access.cpp @@ -0,0 +1,677 @@ +/*************************************************************************/ +/* file_access.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 "file_access.h" + +#include "core/config/project_settings.h" +#include "core/crypto/crypto_core.h" +#include "core/io/file_access_pack.h" +#include "core/io/marshalls.h" +#include "core/os/os.h" + +FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = { nullptr, nullptr }; + +FileAccess::FileCloseFailNotify FileAccess::close_fail_notify = nullptr; + +bool FileAccess::backup_save = false; + +FileAccess *FileAccess::create(AccessType p_access) { + ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr); + + FileAccess *ret = create_func[p_access](); + ret->_set_access_type(p_access); + return ret; +} + +bool FileAccess::exists(const String &p_name) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && PackedData::get_singleton()->has_path(p_name)) { + return true; + } + + FileAccess *f = open(p_name, READ); + if (!f) { + return false; + } + memdelete(f); + return true; +} + +void FileAccess::_set_access_type(AccessType p_access) { + _access_type = p_access; +} + +FileAccess *FileAccess::create_for_path(const String &p_path) { + FileAccess *ret = nullptr; + if (p_path.begins_with("res://")) { + ret = create(ACCESS_RESOURCES); + } else if (p_path.begins_with("user://")) { + ret = create(ACCESS_USERDATA); + + } else { + ret = create(ACCESS_FILESYSTEM); + } + + return ret; +} + +Error FileAccess::reopen(const String &p_path, int p_mode_flags) { + return _open(p_path, p_mode_flags); +} + +FileAccess *FileAccess::open(const String &p_path, int p_mode_flags, Error *r_error) { + //try packed data first + + FileAccess *ret = nullptr; + if (!(p_mode_flags & WRITE) && PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled()) { + ret = PackedData::get_singleton()->try_open_path(p_path); + if (ret) { + if (r_error) { + *r_error = OK; + } + return ret; + } + } + + ret = create_for_path(p_path); + Error err = ret->_open(p_path, p_mode_flags); + + if (r_error) { + *r_error = err; + } + if (err != OK) { + memdelete(ret); + ret = nullptr; + } + + return ret; +} + +FileAccess::CreateFunc FileAccess::get_create_func(AccessType p_access) { + return create_func[p_access]; +} + +String FileAccess::fix_path(const String &p_path) const { + //helper used by file accesses that use a single filesystem + + String r_path = p_path.replace("\\", "/"); + + switch (_access_type) { + case ACCESS_RESOURCES: { + if (ProjectSettings::get_singleton()) { + if (r_path.begins_with("res://")) { + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + if (resource_path != "") { + return r_path.replace("res:/", resource_path); + } + return r_path.replace("res://", ""); + } + } + + } break; + case ACCESS_USERDATA: { + if (r_path.begins_with("user://")) { + String data_dir = OS::get_singleton()->get_user_data_dir(); + if (data_dir != "") { + return r_path.replace("user:/", data_dir); + } + return r_path.replace("user://", ""); + } + + } break; + case ACCESS_FILESYSTEM: { + return r_path; + } break; + case ACCESS_MAX: + break; // Can't happen, but silences warning + } + + return r_path; +} + +/* these are all implemented for ease of porting, then can later be optimized */ + +uint16_t FileAccess::get_16() const { + uint16_t res; + uint8_t a, b; + + a = get_8(); + b = get_8(); + + if (big_endian) { + SWAP(a, b); + } + + res = b; + res <<= 8; + res |= a; + + return res; +} + +uint32_t FileAccess::get_32() const { + uint32_t res; + uint16_t a, b; + + a = get_16(); + b = get_16(); + + if (big_endian) { + SWAP(a, b); + } + + res = b; + res <<= 16; + res |= a; + + return res; +} + +uint64_t FileAccess::get_64() const { + uint64_t res; + uint32_t a, b; + + a = get_32(); + b = get_32(); + + if (big_endian) { + SWAP(a, b); + } + + res = b; + res <<= 32; + res |= a; + + return res; +} + +float FileAccess::get_float() const { + MarshallFloat m; + m.i = get_32(); + return m.f; +} + +real_t FileAccess::get_real() const { + if (real_is_double) { + return get_double(); + } else { + return get_float(); + } +} + +double FileAccess::get_double() const { + MarshallDouble m; + m.l = get_64(); + return m.d; +} + +String FileAccess::get_token() const { + CharString token; + + char32_t c = get_8(); + + while (!eof_reached()) { + if (c <= ' ') { + if (token.length()) { + break; + } + } else { + token += c; + } + c = get_8(); + } + + return String::utf8(token.get_data()); +} + +class CharBuffer { + Vector<char> vector; + char stack_buffer[256]; + + char *buffer = nullptr; + int capacity = 0; + int written = 0; + + bool grow() { + if (vector.resize(next_power_of_2(1 + written)) != OK) { + return false; + } + + if (buffer == stack_buffer) { // first chunk? + + for (int i = 0; i < written; i++) { + vector.write[i] = stack_buffer[i]; + } + } + + buffer = vector.ptrw(); + capacity = vector.size(); + ERR_FAIL_COND_V(written >= capacity, false); + + return true; + } + +public: + _FORCE_INLINE_ CharBuffer() : + buffer(stack_buffer), + capacity(sizeof(stack_buffer) / sizeof(char)) { + } + + _FORCE_INLINE_ void push_back(char c) { + if (written >= capacity) { + ERR_FAIL_COND(!grow()); + } + + buffer[written++] = c; + } + + _FORCE_INLINE_ const char *get_data() const { + return buffer; + } +}; + +String FileAccess::get_line() const { + CharBuffer line; + + char32_t c = get_8(); + + while (!eof_reached()) { + if (c == '\n' || c == '\0') { + line.push_back(0); + return String::utf8(line.get_data()); + } else if (c != '\r') { + line.push_back(c); + } + + c = get_8(); + } + line.push_back(0); + return String::utf8(line.get_data()); +} + +Vector<String> FileAccess::get_csv_line(const String &p_delim) const { + ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines."); + ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines."); + + String line; + + // CSV can support entries with line breaks as long as they are enclosed + // in double quotes. So our "line" might be more than a single line in the + // text file. + int qc = 0; + do { + if (eof_reached()) { + break; + } + line += get_line() + "\n"; + qc = 0; + for (int i = 0; i < line.length(); i++) { + if (line[i] == '"') { + qc++; + } + } + } while (qc % 2); + + // Remove the extraneous newline we've added above. + line = line.substr(0, line.length() - 1); + + Vector<String> strings; + + bool in_quote = false; + String current; + for (int i = 0; i < line.length(); i++) { + char32_t c = line[i]; + // A delimiter ends the current entry, unless it's in a quoted string. + if (!in_quote && c == p_delim[0]) { + strings.push_back(current); + current = String(); + } else if (c == '"') { + // Doubled quotes are escapes for intentional quotes in the string. + if (line[i + 1] == '"' && in_quote) { + current += '"'; + i++; + } else { + in_quote = !in_quote; + } + } else { + current += c; + } + } + strings.push_back(current); + + return strings; +} + +uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + + uint64_t i = 0; + for (i = 0; i < p_length && !eof_reached(); i++) { + p_dst[i] = get_8(); + } + + return i; +} + +String FileAccess::get_as_utf8_string() const { + Vector<uint8_t> sourcef; + uint64_t len = get_length(); + sourcef.resize(len + 1); + + uint8_t *w = sourcef.ptrw(); + uint64_t r = get_buffer(w, len); + ERR_FAIL_COND_V(r != len, String()); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w)) { + return String(); + } + return s; +} + +void FileAccess::store_16(uint16_t p_dest) { + uint8_t a, b; + + a = p_dest & 0xFF; + b = p_dest >> 8; + + if (big_endian) { + SWAP(a, b); + } + + store_8(a); + store_8(b); +} + +void FileAccess::store_32(uint32_t p_dest) { + uint16_t a, b; + + a = p_dest & 0xFFFF; + b = p_dest >> 16; + + if (big_endian) { + SWAP(a, b); + } + + store_16(a); + store_16(b); +} + +void FileAccess::store_64(uint64_t p_dest) { + uint32_t a, b; + + a = p_dest & 0xFFFFFFFF; + b = p_dest >> 32; + + if (big_endian) { + SWAP(a, b); + } + + store_32(a); + store_32(b); +} + +void FileAccess::store_real(real_t p_real) { + if (sizeof(real_t) == 4) { + store_float(p_real); + } else { + store_double(p_real); + } +} + +void FileAccess::store_float(float p_dest) { + MarshallFloat m; + m.f = p_dest; + store_32(m.i); +} + +void FileAccess::store_double(double p_dest) { + MarshallDouble m; + m.d = p_dest; + store_64(m.l); +} + +uint64_t FileAccess::get_modified_time(const String &p_file) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return 0; + } + + FileAccess *fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(!fa, 0, "Cannot create FileAccess for path '" + p_file + "'."); + + uint64_t mt = fa->_get_modified_time(p_file); + memdelete(fa); + return mt; +} + +uint32_t FileAccess::get_unix_permissions(const String &p_file) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return 0; + } + + FileAccess *fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(!fa, 0, "Cannot create FileAccess for path '" + p_file + "'."); + + uint32_t mt = fa->_get_unix_permissions(p_file); + memdelete(fa); + return mt; +} + +Error FileAccess::set_unix_permissions(const String &p_file, uint32_t p_permissions) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + FileAccess *fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'."); + + Error err = fa->_set_unix_permissions(p_file, p_permissions); + memdelete(fa); + return err; +} + +void FileAccess::store_string(const String &p_string) { + if (p_string.length() == 0) { + return; + } + + CharString cs = p_string.utf8(); + store_buffer((uint8_t *)&cs[0], cs.length()); +} + +void FileAccess::store_pascal_string(const String &p_string) { + CharString cs = p_string.utf8(); + store_32(cs.length()); + store_buffer((uint8_t *)&cs[0], cs.length()); +} + +String FileAccess::get_pascal_string() { + uint32_t sl = get_32(); + CharString cs; + cs.resize(sl + 1); + get_buffer((uint8_t *)cs.ptr(), sl); + cs[sl] = 0; + + String ret; + ret.parse_utf8(cs.ptr()); + + return ret; +} + +void FileAccess::store_line(const String &p_line) { + store_string(p_line); + store_8('\n'); +} + +void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_delim) { + ERR_FAIL_COND(p_delim.length() != 1); + + String line = ""; + int size = p_values.size(); + for (int i = 0; i < size; ++i) { + String value = p_values[i]; + + if (value.find("\"") != -1 || value.find(p_delim) != -1 || value.find("\n") != -1) { + value = "\"" + value.replace("\"", "\"\"") + "\""; + } + if (i < size - 1) { + value += p_delim; + } + + line += value; + } + + store_line(line); +} + +void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND(!p_src && p_length > 0); + for (uint64_t i = 0; i < p_length; i++) { + store_8(p_src[i]); + } +} + +Vector<uint8_t> FileAccess::get_file_as_array(const String &p_path, Error *r_error) { + FileAccess *f = FileAccess::open(p_path, READ, r_error); + if (!f) { + if (r_error) { // if error requested, do not throw error + return Vector<uint8_t>(); + } + ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'."); + } + Vector<uint8_t> data; + data.resize(f->get_length()); + f->get_buffer(data.ptrw(), data.size()); + memdelete(f); + return data; +} + +String FileAccess::get_file_as_string(const String &p_path, Error *r_error) { + Error err; + Vector<uint8_t> array = get_file_as_array(p_path, &err); + if (r_error) { + *r_error = err; + } + if (err != OK) { + if (r_error) { + return String(); + } + ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'."); + } + + String ret; + ret.parse_utf8((const char *)array.ptr(), array.size()); + return ret; +} + +String FileAccess::get_md5(const String &p_file) { + FileAccess *f = FileAccess::open(p_file, READ); + if (!f) { + return String(); + } + + CryptoCore::MD5Context ctx; + ctx.start(); + + unsigned char step[32768]; + + while (true) { + uint64_t br = f->get_buffer(step, 32768); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + unsigned char hash[16]; + ctx.finish(hash); + + memdelete(f); + + return String::md5(hash); +} + +String FileAccess::get_multiple_md5(const Vector<String> &p_file) { + CryptoCore::MD5Context ctx; + ctx.start(); + + for (int i = 0; i < p_file.size(); i++) { + FileAccess *f = FileAccess::open(p_file[i], READ); + ERR_CONTINUE(!f); + + unsigned char step[32768]; + + while (true) { + uint64_t br = f->get_buffer(step, 32768); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + memdelete(f); + } + + unsigned char hash[16]; + ctx.finish(hash); + + return String::md5(hash); +} + +String FileAccess::get_sha256(const String &p_file) { + FileAccess *f = FileAccess::open(p_file, READ); + if (!f) { + return String(); + } + + CryptoCore::SHA256Context ctx; + ctx.start(); + + unsigned char step[32768]; + + while (true) { + uint64_t br = f->get_buffer(step, 32768); + if (br > 0) { + ctx.update(step, br); + } + if (br < 4096) { + break; + } + } + + unsigned char hash[32]; + ctx.finish(hash); + + memdelete(f); + return String::hex_encode_buffer(hash, 32); +} diff --git a/core/io/file_access.h b/core/io/file_access.h new file mode 100644 index 0000000000..5804aa2c47 --- /dev/null +++ b/core/io/file_access.h @@ -0,0 +1,198 @@ +/*************************************************************************/ +/* file_access.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 FILE_ACCESS_H +#define FILE_ACCESS_H + +#include "core/math/math_defs.h" +#include "core/os/memory.h" +#include "core/string/ustring.h" +#include "core/typedefs.h" + +/** + * Multi-Platform abstraction for accessing to files. + */ + +class FileAccess { +public: + enum AccessType { + ACCESS_RESOURCES, + ACCESS_USERDATA, + ACCESS_FILESYSTEM, + ACCESS_MAX + }; + + typedef void (*FileCloseFailNotify)(const String &); + + typedef FileAccess *(*CreateFunc)(); + bool big_endian = false; + bool real_is_double = false; + + virtual uint32_t _get_unix_permissions(const String &p_file) = 0; + virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) = 0; + +protected: + String fix_path(const String &p_path) const; + virtual Error _open(const String &p_path, int p_mode_flags) = 0; ///< open a file + virtual uint64_t _get_modified_time(const String &p_file) = 0; + + static FileCloseFailNotify close_fail_notify; + +private: + static bool backup_save; + + AccessType _access_type = ACCESS_FILESYSTEM; + static CreateFunc create_func[ACCESS_MAX]; /** default file access creation function for a platform */ + template <class T> + static FileAccess *_create_builtin() { + return memnew(T); + } + +public: + static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; } + + virtual void _set_access_type(AccessType p_access); + + enum ModeFlags { + READ = 1, + WRITE = 2, + READ_WRITE = 3, + WRITE_READ = 7, + }; + + virtual void close() = 0; ///< close a file + virtual bool is_open() const = 0; ///< true when file is open + + virtual String get_path() const { return ""; } /// returns the path for the current open file + virtual String get_path_absolute() const { return ""; } /// returns the absolute path for the current open file + + virtual void seek(uint64_t p_position) = 0; ///< seek to a given position + virtual void seek_end(int64_t p_position = 0) = 0; ///< seek from the end of file with negative offset + virtual uint64_t get_position() const = 0; ///< get position in the file + virtual uint64_t get_length() const = 0; ///< get size of the file + + virtual bool eof_reached() const = 0; ///< reading passed EOF + + virtual uint8_t get_8() const = 0; ///< get a byte + virtual uint16_t get_16() const; ///< get 16 bits uint + virtual uint32_t get_32() const; ///< get 32 bits uint + virtual uint64_t get_64() const; ///< get 64 bits uint + + virtual float get_float() const; + virtual double get_double() const; + virtual real_t get_real() const; + + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes + virtual String get_line() const; + virtual String get_token() const; + virtual Vector<String> get_csv_line(const String &p_delim = ",") const; + virtual String get_as_utf8_string() const; + + /** + * Use this for files WRITTEN in _big_ endian machines (ie, amiga/mac) + * It's not about the current CPU type but file formats. + * This flag gets reset to `false` (little endian) on each open. + */ + virtual void set_big_endian(bool p_big_endian) { big_endian = p_big_endian; } + inline bool is_big_endian() const { return big_endian; } + + virtual Error get_error() const = 0; ///< get last error + + virtual void flush() = 0; + virtual void store_8(uint8_t p_dest) = 0; ///< store a byte + virtual void store_16(uint16_t p_dest); ///< store 16 bits uint + virtual void store_32(uint32_t p_dest); ///< store 32 bits uint + virtual void store_64(uint64_t p_dest); ///< store 64 bits uint + + virtual void store_float(float p_dest); + virtual void store_double(double p_dest); + virtual void store_real(real_t p_real); + + virtual void store_string(const String &p_string); + virtual void store_line(const String &p_line); + virtual void store_csv_line(const Vector<String> &p_values, const String &p_delim = ","); + + virtual void store_pascal_string(const String &p_string); + virtual String get_pascal_string(); + + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes + + virtual bool file_exists(const String &p_name) = 0; ///< return true if a file exists + + virtual Error reopen(const String &p_path, int p_mode_flags); ///< does not change the AccessType + + static FileAccess *create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files. + static FileAccess *create_for_path(const String &p_path); + static FileAccess *open(const String &p_path, int p_mode_flags, Error *r_error = nullptr); /// Create a file access (for the current platform) this is the only portable way of accessing files. + static CreateFunc get_create_func(AccessType p_access); + static bool exists(const String &p_name); ///< return true if a file exists + static uint64_t get_modified_time(const String &p_file); + static uint32_t get_unix_permissions(const String &p_file); + static Error set_unix_permissions(const String &p_file, uint32_t p_permissions); + + static void set_backup_save(bool p_enable) { backup_save = p_enable; }; + static bool is_backup_save_enabled() { return backup_save; }; + + static String get_md5(const String &p_file); + static String get_sha256(const String &p_file); + static String get_multiple_md5(const Vector<String> &p_file); + + static Vector<uint8_t> get_file_as_array(const String &p_path, Error *r_error = nullptr); + static String get_file_as_string(const String &p_path, Error *r_error = nullptr); + + template <class T> + static void make_default(AccessType p_access) { + create_func[p_access] = _create_builtin<T>; + } + + FileAccess() {} + virtual ~FileAccess() {} +}; + +struct FileAccessRef { + _FORCE_INLINE_ FileAccess *operator->() { + return f; + } + + operator bool() const { return f != nullptr; } + + FileAccess *f; + + operator FileAccess *() { return f; } + + FileAccessRef(FileAccess *fa) { f = fa; } + ~FileAccessRef() { + if (f) { + memdelete(f); + } + } +}; + +#endif // FILE_ACCESS_H diff --git a/core/io/file_access_buffered.cpp b/core/io/file_access_buffered.cpp deleted file mode 100644 index 6208f3a4d1..0000000000 --- a/core/io/file_access_buffered.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/*************************************************************************/ -/* file_access_buffered.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 "file_access_buffered.h" - -#include "core/error_macros.h" - -Error FileAccessBuffered::set_error(Error p_error) const { - return (last_error = p_error); -} - -void FileAccessBuffered::set_cache_size(int p_size) { - cache_size = p_size; -} - -int FileAccessBuffered::get_cache_size() { - return cache_size; -} - -int FileAccessBuffered::cache_data_left() const { - if (file.offset >= file.size) { - return 0; - } - - if (cache.offset == -1 || file.offset < cache.offset || file.offset >= cache.offset + cache.buffer.size()) { - return read_data_block(file.offset, cache_size); - } - - return cache.buffer.size() - (file.offset - cache.offset); -} - -void FileAccessBuffered::seek(size_t p_position) { - file.offset = p_position; -} - -void FileAccessBuffered::seek_end(int64_t p_position) { - file.offset = file.size + p_position; -} - -size_t FileAccessBuffered::get_position() const { - return file.offset; -} - -size_t FileAccessBuffered::get_len() const { - return file.size; -} - -bool FileAccessBuffered::eof_reached() const { - return file.offset > file.size; -} - -uint8_t FileAccessBuffered::get_8() const { - ERR_FAIL_COND_V_MSG(!file.open, 0, "Can't get data, when file is not opened."); - - uint8_t byte = 0; - if (cache_data_left() >= 1) { - byte = cache.buffer[file.offset - cache.offset]; - } - - ++file.offset; - - return byte; -} - -int FileAccessBuffered::get_buffer(uint8_t *p_dest, int p_length) const { - ERR_FAIL_COND_V_MSG(!file.open, -1, "Can't get buffer, when file is not opened."); - - if (p_length > cache_size) { - int total_read = 0; - - if (!(cache.offset == -1 || file.offset < cache.offset || file.offset >= cache.offset + cache.buffer.size())) { - int size = (cache.buffer.size() - (file.offset - cache.offset)); - size = size - (size % 4); - //const uint8_t* read = cache.buffer.ptr(); - //memcpy(p_dest, read.ptr() + (file.offset - cache.offset), size); - memcpy(p_dest, cache.buffer.ptr() + (file.offset - cache.offset), size); - p_dest += size; - p_length -= size; - file.offset += size; - total_read += size; - } - - int err = read_data_block(file.offset, p_length, p_dest); - if (err >= 0) { - total_read += err; - file.offset += err; - } - - return total_read; - } - - int to_read = p_length; - int total_read = 0; - while (to_read > 0) { - int left = cache_data_left(); - if (left == 0) { - file.offset += to_read; - return total_read; - } - if (left < 0) { - return left; - } - - int r = MIN(left, to_read); - //const uint8_t* read = cache.buffer.ptr(); - //memcpy(p_dest+total_read, &read.ptr()[file.offset - cache.offset], r); - memcpy(p_dest + total_read, cache.buffer.ptr() + (file.offset - cache.offset), r); - - file.offset += r; - total_read += r; - to_read -= r; - } - - return p_length; -} - -bool FileAccessBuffered::is_open() const { - return file.open; -} - -Error FileAccessBuffered::get_error() const { - return last_error; -} diff --git a/core/io/file_access_buffered_fa.h b/core/io/file_access_buffered_fa.h deleted file mode 100644 index f22e54e154..0000000000 --- a/core/io/file_access_buffered_fa.h +++ /dev/null @@ -1,139 +0,0 @@ -/*************************************************************************/ -/* file_access_buffered_fa.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 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 FILE_ACCESS_BUFFERED_FA_H -#define FILE_ACCESS_BUFFERED_FA_H - -#include "core/io/file_access_buffered.h" - -template <class T> -class FileAccessBufferedFA : public FileAccessBuffered { - T f; - - int read_data_block(int p_offset, int p_size, uint8_t *p_dest = 0) const { - ERR_FAIL_COND_V_MSG(!f.is_open(), -1, "Can't read data block when file is not opened."); - - ((T *)&f)->seek(p_offset); - - if (p_dest) { - f.get_buffer(p_dest, p_size); - return p_size; - - } else { - cache.offset = p_offset; - cache.buffer.resize(p_size); - - // on Vector - //uint8_t* write = cache.buffer.ptrw(); - //f.get_buffer(write.ptrw(), p_size); - - // on vector - f.get_buffer(cache.buffer.ptrw(), p_size); - - return p_size; - } - } - - static FileAccess *create() { - return memnew(FileAccessBufferedFA<T>()); - } - -protected: - virtual void _set_access_type(AccessType p_access) { - f._set_access_type(p_access); - FileAccessBuffered::_set_access_type(p_access); - } - -public: - void flush() { - f.flush(); - } - - void store_8(uint8_t p_dest) { - f.store_8(p_dest); - } - - void store_buffer(const uint8_t *p_src, int p_length) { - f.store_buffer(p_src, p_length); - } - - bool file_exists(const String &p_name) { - return f.file_exists(p_name); - } - - Error _open(const String &p_path, int p_mode_flags) { - close(); - - Error ret = f._open(p_path, p_mode_flags); - if (ret != OK) - return ret; - //ERR_FAIL_COND_V( ret != OK, ret ); - - file.size = f.get_len(); - file.offset = 0; - file.open = true; - file.name = p_path; - file.access_flags = p_mode_flags; - - cache.buffer.resize(0); - cache.offset = 0; - - return set_error(OK); - } - - void close() { - f.close(); - - file.offset = 0; - file.size = 0; - file.open = false; - file.name = ""; - - cache.buffer.resize(0); - cache.offset = 0; - set_error(OK); - } - - virtual uint64_t _get_modified_time(const String &p_file) { - return f._get_modified_time(p_file); - } - - virtual uint32_t _get_unix_permissions(const String &p_file) { - return f._get_unix_permissions(p_file); - } - - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { - return f._set_unix_permissions(p_file, p_permissions); - } - - FileAccessBufferedFA() {} -}; - -#endif // FILE_ACCESS_BUFFERED_FA_H diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index 7817ccb773..e54c947340 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,9 @@ #include "file_access_compressed.h" -#include "core/print_string.h" +#include "core/string/print_string.h" -void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, int p_block_size) { +void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) { magic = p_magic.ascii().get_data(); if (magic.length() > 4) { magic = magic.substr(0, 4); @@ -67,10 +67,10 @@ Error FileAccessCompressed::open_after_magic(FileAccess *p_base) { ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted."); } read_total = f->get_32(); - int bc = (read_total / block_size) + 1; - int acc_ofs = f->get_position() + bc * 4; - int max_bs = 0; - for (int i = 0; i < bc; i++) { + uint32_t bc = (read_total / block_size) + 1; + uint64_t acc_ofs = f->get_position() + bc * 4; + uint32_t max_bs = 0; + for (uint32_t i = 0; i < bc; i++) { ReadBlock rb; rb.offset = acc_ofs; rb.csize = f->get_32(); @@ -148,15 +148,15 @@ void FileAccessCompressed::close() { f->store_32(cmode); //write compression mode 4 f->store_32(block_size); //write block size 4 f->store_32(write_max); //max amount of data written 4 - int bc = (write_max / block_size) + 1; + uint32_t bc = (write_max / block_size) + 1; - for (int i = 0; i < bc; i++) { + for (uint32_t i = 0; i < bc; i++) { f->store_32(0); //compressed sizes, will update later } Vector<int> block_sizes; - for (int i = 0; i < bc; i++) { - int bl = i == (bc - 1) ? write_max % block_size : block_size; + for (uint32_t i = 0; i < bc; i++) { + uint32_t bl = i == (bc - 1) ? write_max % block_size : block_size; uint8_t *bp = &write_ptr[i * block_size]; Vector<uint8_t> cblock; @@ -168,7 +168,7 @@ void FileAccessCompressed::close() { } f->seek(16); //ok write block sizes - for (int i = 0; i < bc; i++) { + for (uint32_t i = 0; i < bc; i++) { f->store_32(block_sizes[i]); } f->seek_end(); @@ -190,8 +190,9 @@ bool FileAccessCompressed::is_open() const { return f != nullptr; } -void FileAccessCompressed::seek(size_t p_position) { +void FileAccessCompressed::seek(uint64_t p_position) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); + if (writing) { ERR_FAIL_COND(p_position > write_max); @@ -204,7 +205,7 @@ void FileAccessCompressed::seek(size_t p_position) { } else { at_end = false; read_eof = false; - int block_idx = p_position / block_size; + uint32_t block_idx = p_position / block_size; if (block_idx != read_block) { read_block = block_idx; f->seek(read_blocks[read_block].offset); @@ -227,7 +228,7 @@ void FileAccessCompressed::seek_end(int64_t p_position) { } } -size_t FileAccessCompressed::get_position() const { +uint64_t FileAccessCompressed::get_position() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); if (writing) { return write_pos; @@ -236,7 +237,7 @@ size_t FileAccessCompressed::get_position() const { } } -size_t FileAccessCompressed::get_len() const { +uint64_t FileAccessCompressed::get_length() const { ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); if (writing) { return write_max; @@ -285,16 +286,17 @@ uint8_t FileAccessCompressed::get_8() const { return ret; } -int FileAccessCompressed::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V_MSG(!f, 0, "File must be opened before use."); - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); +uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V_MSG(!f, -1, "File must be opened before use."); + ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode."); if (at_end) { read_eof = true; return 0; } - for (int i = 0; i < p_length; i++) { + for (uint64_t i = 0; i < p_length; i++) { p_dst[i] = read_ptr[read_pos]; read_pos++; if (read_pos >= read_block_size) { @@ -327,14 +329,14 @@ Error FileAccessCompressed::get_error() const { void FileAccessCompressed::flush() { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); - ERR_FAIL_COND_MSG(!writing, "File has not been opened in read mode."); + ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); // compressed files keep data in memory till close() } void FileAccessCompressed::store_8(uint8_t p_dest) { ERR_FAIL_COND_MSG(!f, "File must be opened before use."); - ERR_FAIL_COND_MSG(!writing, "File has not been opened in read mode."); + ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); WRITE_FIT(1); write_ptr[write_pos++] = p_dest; diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h index 52284b347e..3389e020e3 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,39 +32,39 @@ #define FILE_ACCESS_COMPRESSED_H #include "core/io/compression.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" class FileAccessCompressed : public FileAccess { Compression::Mode cmode = Compression::MODE_ZSTD; bool writing = false; - uint32_t write_pos = 0; + uint64_t write_pos = 0; uint8_t *write_ptr = nullptr; uint32_t write_buffer_size = 0; - uint32_t write_max = 0; + uint64_t write_max = 0; uint32_t block_size = 0; mutable bool read_eof = false; mutable bool at_end = false; struct ReadBlock { - int csize; - int offset; + uint32_t csize; + uint64_t offset; }; mutable Vector<uint8_t> comp_buffer; uint8_t *read_ptr = nullptr; - mutable int read_block = 0; - int read_block_count = 0; - mutable int read_block_size = 0; - mutable int read_pos = 0; + mutable uint32_t read_block = 0; + uint32_t read_block_count = 0; + mutable uint32_t read_block_size = 0; + mutable uint64_t read_pos = 0; Vector<ReadBlock> read_blocks; - uint32_t read_total = 0; + uint64_t read_total = 0; String magic = "GCMP"; mutable Vector<uint8_t> buffer; FileAccess *f = nullptr; public: - void configure(const String &p_magic, Compression::Mode p_mode = Compression::MODE_ZSTD, int p_block_size = 4096); + void configure(const String &p_magic, Compression::Mode p_mode = Compression::MODE_ZSTD, uint32_t p_block_size = 4096); Error open_after_magic(FileAccess *p_base); @@ -72,15 +72,15 @@ public: virtual void close(); ///< close a file virtual bool is_open() const; ///< true when file is open - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_length() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; virtual Error get_error() const; ///< get last error diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index eb684f457e..9e316291e8 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,8 @@ #include "file_access_encrypted.h" #include "core/crypto/crypto_core.h" -#include "core/os/copymem.h" -#include "core/print_string.h" -#include "core/variant.h" +#include "core/string/print_string.h" +#include "core/variant/variant.h" #include <stdio.h> @@ -70,14 +69,14 @@ Error FileAccessEncrypted::open_and_parse(FileAccess *p_base, const Vector<uint8 } base = p_base->get_position(); - ERR_FAIL_COND_V(p_base->get_len() < base + length, ERR_FILE_CORRUPT); - uint32_t ds = length; + ERR_FAIL_COND_V(p_base->get_length() < base + length, ERR_FILE_CORRUPT); + uint64_t ds = length; if (ds % 16) { ds += 16 - (ds % 16); } data.resize(ds); - uint32_t blen = p_base->get_buffer(data.ptrw(), ds); + uint64_t blen = p_base->get_buffer(data.ptrw(), ds); ERR_FAIL_COND_V(blen != ds, ERR_FILE_CORRUPT); { @@ -142,7 +141,7 @@ void FileAccessEncrypted::release() { void FileAccessEncrypted::_release() { if (writing) { Vector<uint8_t> compressed; - size_t len = data.size(); + uint64_t len = data.size(); if (len % 16) { len += 16 - (len % 16); } @@ -151,7 +150,7 @@ void FileAccessEncrypted::_release() { ERR_FAIL_COND(CryptoCore::md5(data.ptr(), data.size(), hash) != OK); // Bug? compressed.resize(len); - zeromem(compressed.ptrw(), len); + memset(compressed.ptrw(), 0, len); for (int i = 0; i < data.size(); i++) { compressed.write[i] = data[i]; } @@ -199,9 +198,9 @@ String FileAccessEncrypted::get_path_absolute() const { } } -void FileAccessEncrypted::seek(size_t p_position) { - if (p_position > (size_t)data.size()) { - p_position = data.size(); +void FileAccessEncrypted::seek(uint64_t p_position) { + if (p_position > get_length()) { + p_position = get_length(); } pos = p_position; @@ -209,14 +208,14 @@ void FileAccessEncrypted::seek(size_t p_position) { } void FileAccessEncrypted::seek_end(int64_t p_position) { - seek(data.size() + p_position); + seek(get_length() + p_position); } -size_t FileAccessEncrypted::get_position() const { +uint64_t FileAccessEncrypted::get_position() const { return pos; } -size_t FileAccessEncrypted::get_len() const { +uint64_t FileAccessEncrypted::get_length() const { return data.size(); } @@ -226,7 +225,7 @@ bool FileAccessEncrypted::eof_reached() const { uint8_t FileAccessEncrypted::get_8() const { ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); - if (pos >= data.size()) { + if (pos >= get_length()) { eofed = true; return 0; } @@ -236,11 +235,12 @@ uint8_t FileAccessEncrypted::get_8() const { return b; } -int FileAccessEncrypted::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); +uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode."); - int to_copy = MIN(p_length, data.size() - pos); - for (int i = 0; i < to_copy; i++) { + uint64_t to_copy = MIN(p_length, get_length() - pos); + for (uint64_t i = 0; i < to_copy; i++) { p_dst[i] = data[pos++]; } @@ -255,16 +255,17 @@ Error FileAccessEncrypted::get_error() const { return eofed ? ERR_FILE_EOF : OK; } -void FileAccessEncrypted::store_buffer(const uint8_t *p_src, int p_length) { - ERR_FAIL_COND_MSG(!writing, "File has not been opened in read mode."); +void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); + ERR_FAIL_COND(!p_src && p_length > 0); - if (pos < data.size()) { - for (int i = 0; i < p_length; i++) { + if (pos < get_length()) { + for (uint64_t i = 0; i < p_length; i++) { store_8(p_src[i]); } - } else if (pos == data.size()) { + } else if (pos == get_length()) { data.resize(pos + p_length); - for (int i = 0; i < p_length; i++) { + for (uint64_t i = 0; i < p_length; i++) { data.write[pos + i] = p_src[i]; } pos += p_length; @@ -272,18 +273,18 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, int p_length) { } void FileAccessEncrypted::flush() { - ERR_FAIL_COND_MSG(!writing, "File has not been opened in read mode."); + ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); // encrypted files keep data in memory till close() } void FileAccessEncrypted::store_8(uint8_t p_dest) { - ERR_FAIL_COND_MSG(!writing, "File has not been opened in read mode."); + ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); - if (pos < data.size()) { + if (pos < get_length()) { data.write[pos] = p_dest; pos++; - } else if (pos == data.size()) { + } else if (pos == get_length()) { data.push_back(p_dest); pos++; } diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index fddc6842f3..decffae696 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,7 @@ #ifndef FILE_ACCESS_ENCRYPTED_H #define FILE_ACCESS_ENCRYPTED_H -#include "core/os/file_access.h" +#include "core/io/file_access.h" #define ENCRYPTED_HEADER_MAGIC 0x43454447 @@ -47,10 +47,10 @@ private: Vector<uint8_t> key; bool writing = false; FileAccess *file = nullptr; - size_t base; - size_t length; + uint64_t base = 0; + uint64_t length = 0; Vector<uint8_t> data; - mutable int pos = 0; + mutable uint64_t pos = 0; mutable bool eofed = false; bool use_magic = true; @@ -68,21 +68,21 @@ public: virtual String get_path() const; /// returns the path for the current open file virtual String get_path_absolute() const; /// returns the absolute path for the current open file - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_length() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; virtual Error get_error() const; ///< get last error virtual void flush(); virtual void store_8(uint8_t p_dest); ///< store a byte - virtual void store_buffer(const uint8_t *p_src, int p_length); ///< store an array of bytes + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes virtual bool file_exists(const String &p_name); ///< return true if a file exists diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp index a65ff92a89..627fd2bf9c 100644 --- a/core/io/file_access_memory.cpp +++ b/core/io/file_access_memory.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,10 +30,9 @@ #include "file_access_memory.h" -#include "core/map.h" -#include "core/os/copymem.h" -#include "core/os/dir_access.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/templates/map.h" static Map<String, Vector<uint8_t>> *files = nullptr; @@ -72,7 +71,7 @@ bool FileAccessMemory::file_exists(const String &p_name) { return files && (files->find(name) != nullptr); } -Error FileAccessMemory::open_custom(const uint8_t *p_data, int p_len) { +Error FileAccessMemory::open_custom(const uint8_t *p_data, uint64_t p_len) { data = (uint8_t *)p_data; length = p_len; pos = 0; @@ -103,7 +102,7 @@ bool FileAccessMemory::is_open() const { return data != nullptr; } -void FileAccessMemory::seek(size_t p_position) { +void FileAccessMemory::seek(uint64_t p_position) { ERR_FAIL_COND(!data); pos = p_position; } @@ -113,12 +112,12 @@ void FileAccessMemory::seek_end(int64_t p_position) { pos = length + p_position; } -size_t FileAccessMemory::get_position() const { +uint64_t FileAccessMemory::get_position() const { ERR_FAIL_COND_V(!data, 0); return pos; } -size_t FileAccessMemory::get_len() const { +uint64_t FileAccessMemory::get_length() const { ERR_FAIL_COND_V(!data, 0); return length; } @@ -137,17 +136,18 @@ uint8_t FileAccessMemory::get_8() const { return ret; } -int FileAccessMemory::get_buffer(uint8_t *p_dst, int p_length) const { +uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V(!data, -1); - int left = length - pos; - int read = MIN(p_length, left); + uint64_t left = length - pos; + uint64_t read = MIN(p_length, left); if (read < p_length) { WARN_PRINT("Reading less data than requested"); } - copymem(p_dst, &data[pos], read); + memcpy(p_dst, &data[pos], read); pos += p_length; return read; @@ -167,13 +167,14 @@ void FileAccessMemory::store_8(uint8_t p_byte) { data[pos++] = p_byte; } -void FileAccessMemory::store_buffer(const uint8_t *p_src, int p_length) { - int left = length - pos; - int write = MIN(p_length, left); +void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) { + ERR_FAIL_COND(!p_src && p_length > 0); + uint64_t left = length - pos; + uint64_t write = MIN(p_length, left); if (write < p_length) { WARN_PRINT("Writing less data than requested"); } - copymem(&data[pos], p_src, write); + memcpy(&data[pos], p_src, write); pos += p_length; } diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h index 1a9bd3fbbb..14135bd68c 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,12 +31,12 @@ #ifndef FILE_ACCESS_MEMORY_H #define FILE_ACCESS_MEMORY_H -#include "core/os/file_access.h" +#include "core/io/file_access.h" class FileAccessMemory : public FileAccess { uint8_t *data = nullptr; - int length; - mutable int pos; + uint64_t length = 0; + mutable uint64_t pos = 0; static FileAccess *create(); @@ -44,27 +44,27 @@ public: static void register_file(String p_name, Vector<uint8_t> p_data); static void cleanup(); - virtual Error open_custom(const uint8_t *p_data, int p_len); ///< open a file + virtual Error open_custom(const uint8_t *p_data, uint64_t p_len); ///< open a file virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file virtual void close(); ///< close a file virtual bool is_open() const; ///< true when file is open - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_length() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; ///< get an array of bytes + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes virtual Error get_error() const; ///< get last error virtual void flush(); virtual void store_8(uint8_t p_byte); ///< store a byte - virtual void store_buffer(const uint8_t *p_src, int p_length); ///< store an array of bytes + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes virtual bool file_exists(const String &p_name); ///< return true if a file exists diff --git a/core/io/file_access_network.cpp b/core/io/file_access_network.cpp index 6890740d90..9ee3876c2f 100644 --- a/core/io/file_access_network.cpp +++ b/core/io/file_access_network.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,10 +30,10 @@ #include "file_access_network.h" +#include "core/config/project_settings.h" #include "core/io/ip.h" #include "core/io/marshalls.h" #include "core/os/os.h" -#include "core/project_settings.h" //#define DEBUG_PRINT(m_p) print_line(m_p) //#define DEBUG_TIME(m_what) printf("MS: %s - %lli\n",m_what,OS::get_singleton()->get_ticks_usec()); @@ -126,7 +126,7 @@ void FileAccessNetworkClient::_thread_func() { if (status != OK) { fa->_respond(0, Error(status)); } else { - uint64_t len = get_64(); + int64_t len = get_64(); fa->_respond(len, Error(status)); } @@ -135,7 +135,7 @@ void FileAccessNetworkClient::_thread_func() { } break; case FileAccessNetwork::RESPONSE_DATA: { int64_t offset = get_64(); - uint32_t len = get_32(); + int32_t len = get_32(); Vector<uint8_t> block; block.resize(len); @@ -171,7 +171,7 @@ void FileAccessNetworkClient::_thread_func(void *s) { } Error FileAccessNetworkClient::connect(const String &p_host, int p_port, const String &p_password) { - IP_Address ip; + IPAddress ip; if (p_host.is_valid_ip_address()) { ip = p_host; @@ -201,7 +201,7 @@ Error FileAccessNetworkClient::connect(const String &p_host, int p_port, const S return ERR_INVALID_PARAMETER; } - thread = Thread::create(_thread_func, this); + thread.start(_thread_func, this); return OK; } @@ -210,25 +210,22 @@ FileAccessNetworkClient *FileAccessNetworkClient::singleton = nullptr; FileAccessNetworkClient::FileAccessNetworkClient() { singleton = this; - client.instance(); + client.instantiate(); } FileAccessNetworkClient::~FileAccessNetworkClient() { - if (thread) { - quit = true; - sem.post(); - Thread::wait_to_finish(thread); - memdelete(thread); - } + quit = true; + sem.post(); + thread.wait_to_finish(); } -void FileAccessNetwork::_set_block(int p_offset, const Vector<uint8_t> &p_block) { - int page = p_offset / page_size; +void FileAccessNetwork::_set_block(uint64_t p_offset, const Vector<uint8_t> &p_block) { + int32_t page = p_offset / page_size; ERR_FAIL_INDEX(page, pages.size()); if (page < pages.size() - 1) { ERR_FAIL_COND(p_block.size() != page_size); } else { - ERR_FAIL_COND((p_block.size() != (int)(total_size % page_size))); + ERR_FAIL_COND((uint64_t)p_block.size() != total_size % page_size); } { @@ -243,7 +240,7 @@ void FileAccessNetwork::_set_block(int p_offset, const Vector<uint8_t> &p_block) } } -void FileAccessNetwork::_respond(size_t p_len, Error p_status) { +void FileAccessNetwork::_respond(uint64_t p_len, Error p_status) { DEBUG_PRINT("GOT RESPONSE - len: " + itos(p_len) + " status: " + itos(p_status)); response = p_status; if (response != OK) { @@ -251,7 +248,7 @@ void FileAccessNetwork::_respond(size_t p_len, Error p_status) { } opened = true; total_size = p_len; - int pc = ((total_size - 1) / page_size) + 1; + int32_t pc = ((total_size - 1) / page_size) + 1; pages.resize(pc); } @@ -310,8 +307,9 @@ bool FileAccessNetwork::is_open() const { return opened; } -void FileAccessNetwork::seek(size_t p_position) { +void FileAccessNetwork::seek(uint64_t p_position) { ERR_FAIL_COND_MSG(!opened, "File must be opened before use."); + eof_flag = p_position > total_size; if (p_position >= total_size) { @@ -325,12 +323,12 @@ void FileAccessNetwork::seek_end(int64_t p_position) { seek(total_size + p_position); } -size_t FileAccessNetwork::get_position() const { +uint64_t FileAccessNetwork::get_position() const { ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use."); return pos; } -size_t FileAccessNetwork::get_len() const { +uint64_t FileAccessNetwork::get_length() const { ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use."); return total_size; } @@ -346,18 +344,18 @@ uint8_t FileAccessNetwork::get_8() const { return v; } -void FileAccessNetwork::_queue_page(int p_page) const { +void FileAccessNetwork::_queue_page(int32_t p_page) const { if (p_page >= pages.size()) { return; } - if (pages[p_page].buffer.empty() && !pages[p_page].queued) { + if (pages[p_page].buffer.is_empty() && !pages[p_page].queued) { FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; { MutexLock lock(nc->blockrequest_mutex); FileAccessNetworkClient::BlockRequest br; br.id = id; - br.offset = size_t(p_page) * page_size; + br.offset = (uint64_t)p_page * page_size; br.size = page_size; nc->block_requests.push_back(br); pages.write[p_page].queued = true; @@ -368,8 +366,9 @@ void FileAccessNetwork::_queue_page(int p_page) const { } } -int FileAccessNetwork::get_buffer(uint8_t *p_dst, int p_length) const { - //bool eof=false; +uint64_t FileAccessNetwork::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + if (pos + p_length > total_size) { eof_flag = true; } @@ -377,18 +376,16 @@ int FileAccessNetwork::get_buffer(uint8_t *p_dst, int p_length) const { p_length = total_size - pos; } - //FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton; - uint8_t *buff = last_page_buff; - for (int i = 0; i < p_length; i++) { - int page = pos / page_size; + for (uint64_t i = 0; i < p_length; i++) { + int32_t page = pos / page_size; if (page != last_page) { buffer_mutex.lock(); - if (pages[page].buffer.empty()) { + if (pages[page].buffer.is_empty()) { waiting_on_page = page; - for (int j = 0; j < read_ahead; j++) { + for (int32_t j = 0; j < read_ahead; j++) { _queue_page(page + j); } buffer_mutex.unlock(); @@ -396,10 +393,9 @@ int FileAccessNetwork::get_buffer(uint8_t *p_dst, int p_length) const { page_sem.wait(); DEBUG_PRINT("done"); } else { - for (int j = 0; j < read_ahead; j++) { + for (int32_t j = 0; j < read_ahead; j++) { _queue_page(page + j); } - //queue pages buffer_mutex.unlock(); } diff --git a/core/io/file_access_network.h b/core/io/file_access_network.h index dc5ce1e883..1d9d761fbb 100644 --- a/core/io/file_access_network.h +++ b/core/io/file_access_network.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,8 +31,8 @@ #ifndef FILE_ACCESS_NETWORK_H #define FILE_ACCESS_NETWORK_H +#include "core/io/file_access.h" #include "core/io/stream_peer_tcp.h" -#include "core/os/file_access.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -40,31 +40,31 @@ class FileAccessNetwork; class FileAccessNetworkClient { struct BlockRequest { - int id; + int32_t id; uint64_t offset; - int size; + int32_t size; }; List<BlockRequest> block_requests; Semaphore sem; - Thread *thread = nullptr; + Thread thread; bool quit = false; Mutex mutex; Mutex blockrequest_mutex; Map<int, FileAccessNetwork *> accesses; Ref<StreamPeerTCP> client; - int last_id = 0; - int lockcount = 0; + int32_t last_id = 0; + int32_t lockcount = 0; Vector<uint8_t> block; void _thread_func(); static void _thread_func(void *s); - void put_32(int p_32); + void put_32(int32_t p_32); void put_64(int64_t p_64); - int get_32(); + int32_t get_32(); int64_t get_64(); void lock_mutex(); void unlock_mutex(); @@ -86,15 +86,15 @@ class FileAccessNetwork : public FileAccess { Semaphore page_sem; Mutex buffer_mutex; bool opened = false; - size_t total_size; - mutable size_t pos = 0; - int id; + uint64_t total_size; + mutable uint64_t pos = 0; + int32_t id; mutable bool eof_flag = false; - mutable int last_page = -1; + mutable int32_t last_page = -1; mutable uint8_t *last_page_buff = nullptr; - int page_size; - int read_ahead; + int32_t page_size; + int32_t read_ahead; mutable int waiting_on_page = -1; @@ -110,9 +110,9 @@ class FileAccessNetwork : public FileAccess { uint64_t exists_modtime; friend class FileAccessNetworkClient; - void _queue_page(int p_page) const; - void _respond(size_t p_len, Error p_status); - void _set_block(int p_offset, const Vector<uint8_t> &p_block); + void _queue_page(int32_t p_page) const; + void _respond(uint64_t p_len, Error p_status); + void _set_block(uint64_t p_offset, const Vector<uint8_t> &p_block); public: enum Command { @@ -134,15 +134,15 @@ public: virtual void close(); ///< close a file virtual bool is_open() const; ///< true when file is open - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_length() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; virtual Error get_error() const; ///< get last error diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 8fdbb650d4..7b43daf9c0 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,12 +31,12 @@ #include "file_access_pack.h" #include "core/io/file_access_encrypted.h" -#include "core/script_language.h" +#include "core/object/script_language.h" #include "core/version.h" #include <stdio.h> -Error PackedData::add_pack(const String &p_path, bool p_replace_files, size_t p_offset) { +Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) { for (int i = 0; i < sources.size(); i++) { if (sources[i]->try_open_pack(p_path, p_replace_files, p_offset)) { return OK; @@ -46,17 +46,16 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, size_t p_ return ERR_FILE_UNRECOGNIZED; } -void PackedData::add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) { - PathMD5 pmd5(path.md5_buffer()); - //printf("adding path %s, %lli, %lli\n", path.utf8().get_data(), pmd5.a, pmd5.b); +void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) { + PathMD5 pmd5(p_path.md5_buffer()); bool exists = files.has(pmd5); PackedFile pf; pf.encrypted = p_encrypted; - pf.pack = pkg_path; - pf.offset = ofs; - pf.size = size; + pf.pack = p_pkg_path; + pf.offset = p_ofs; + pf.size = p_size; for (int i = 0; i < 16; i++) { pf.md5[i] = p_md5[i]; } @@ -68,7 +67,7 @@ void PackedData::add_path(const String &pkg_path, const String &path, uint64_t o if (!exists) { //search for dir - String p = path.replace_first("res://", ""); + String p = p_path.replace_first("res://", ""); PackedDir *cd = root; if (p.find("/") != -1) { //in a subdir @@ -87,9 +86,9 @@ void PackedData::add_path(const String &pkg_path, const String &path, uint64_t o } } } - String filename = path.get_file(); + String filename = p_path.get_file(); // Don't add as a file if the path points to a directory - if (!filename.empty()) { + if (!filename.is_empty()) { cd->files.insert(filename); } } @@ -126,7 +125,7 @@ PackedData::~PackedData() { ////////////////////////////////////////////////////////////////// -bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, size_t p_offset) { +bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { return false; @@ -261,7 +260,7 @@ bool FileAccessPack::is_open() const { return f->is_open(); } -void FileAccessPack::seek(size_t p_position) { +void FileAccessPack::seek(uint64_t p_position) { if (p_position > pf.size) { eof = true; } else { @@ -276,11 +275,11 @@ void FileAccessPack::seek_end(int64_t p_position) { seek(pf.size + p_position); } -size_t FileAccessPack::get_position() const { +uint64_t FileAccessPack::get_position() const { return pos; } -size_t FileAccessPack::get_len() const { +uint64_t FileAccessPack::get_length() const { return pf.size; } @@ -298,15 +297,17 @@ uint8_t FileAccessPack::get_8() const { return f->get_8(); } -int FileAccessPack::get_buffer(uint8_t *p_dst, int p_length) const { +uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); + if (eof) { return 0; } - uint64_t to_read = p_length; + int64_t to_read = p_length; if (to_read + pos > pf.size) { eof = true; - to_read = int64_t(pf.size) - int64_t(pos); + to_read = (int64_t)pf.size - (int64_t)pos; } pos += p_length; @@ -319,9 +320,9 @@ int FileAccessPack::get_buffer(uint8_t *p_dst, int p_length) const { return to_read; } -void FileAccessPack::set_endian_swap(bool p_swap) { - FileAccess::set_endian_swap(p_swap); - f->set_endian_swap(p_swap); +void FileAccessPack::set_big_endian(bool p_big_endian) { + FileAccess::set_big_endian(p_big_endian); + f->set_big_endian(p_big_endian); } Error FileAccessPack::get_error() const { @@ -339,7 +340,7 @@ void FileAccessPack::store_8(uint8_t p_dest) { ERR_FAIL(); } -void FileAccessPack::store_buffer(const uint8_t *p_src, int p_length) { +void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } @@ -442,8 +443,14 @@ String DirAccessPack::get_drive(int p_drive) { return ""; } -Error DirAccessPack::change_dir(String p_dir) { +PackedData::PackedDir *DirAccessPack::_find_dir(String p_dir) { String nd = p_dir.replace("\\", "/"); + + // Special handling since simplify_path() will forbid it + if (p_dir == "..") { + return current->parent; + } + bool absolute = false; if (nd.begins_with("res://")) { nd = nd.replace_first("res://", ""); @@ -483,13 +490,21 @@ Error DirAccessPack::change_dir(String p_dir) { pd = pd->subdirs[p]; } else { - return ERR_INVALID_PARAMETER; + return nullptr; } } - current = pd; + return pd; +} - return OK; +Error DirAccessPack::change_dir(String p_dir) { + PackedData::PackedDir *pd = _find_dir(p_dir); + if (pd) { + current = pd; + return OK; + } else { + return ERR_INVALID_PARAMETER; + } } String DirAccessPack::get_current_dir(bool p_include_drive) { @@ -507,13 +522,17 @@ String DirAccessPack::get_current_dir(bool p_include_drive) { bool DirAccessPack::file_exists(String p_file) { p_file = fix_path(p_file); - return current->files.has(p_file); + PackedData::PackedDir *pd = _find_dir(p_file.get_base_dir()); + if (!pd) { + return false; + } + return pd->files.has(p_file.get_file()); } bool DirAccessPack::dir_exists(String p_dir) { p_dir = fix_path(p_dir); - return current->subdirs.has(p_dir); + return _find_dir(p_dir) != nullptr; } Error DirAccessPack::make_dir(String p_dir) { @@ -528,7 +547,7 @@ Error DirAccessPack::remove(String p_name) { return ERR_UNAVAILABLE; } -size_t DirAccessPack::get_space_left() { +uint64_t DirAccessPack::get_space_left() { return 0; } diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index d934b0deb5..2f0ee62723 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,11 +31,12 @@ #ifndef FILE_ACCESS_PACK_H #define FILE_ACCESS_PACK_H -#include "core/list.h" -#include "core/map.h" -#include "core/os/dir_access.h" -#include "core/os/file_access.h" -#include "core/print_string.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/string/print_string.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/set.h" // Godot's packed file magic header ("GDPC" in ASCII). #define PACK_HEADER_MAGIC 0x43504447 @@ -92,7 +93,7 @@ private: PathMD5() {} - PathMD5(const Vector<uint8_t> p_buf) { + PathMD5(const Vector<uint8_t> &p_buf) { a = *((uint64_t *)&p_buf[0]); b = *((uint64_t *)&p_buf[8]); } @@ -111,38 +112,41 @@ private: public: void add_pack_source(PackSource *p_source); - void add_path(const String &pkg_path, const String &path, uint64_t ofs, uint64_t size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource + void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource void set_disabled(bool p_disabled) { disabled = p_disabled; } _FORCE_INLINE_ bool is_disabled() const { return disabled; } static PackedData *get_singleton() { return singleton; } - Error add_pack(const String &p_path, bool p_replace_files, size_t p_offset); + Error add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset); _FORCE_INLINE_ FileAccess *try_open_path(const String &p_path); _FORCE_INLINE_ bool has_path(const String &p_path); + _FORCE_INLINE_ DirAccess *try_open_directory(const String &p_path); + _FORCE_INLINE_ bool has_directory(const String &p_path); + PackedData(); ~PackedData(); }; class PackSource { public: - virtual bool try_open_pack(const String &p_path, bool p_replace_files, size_t p_offset) = 0; + virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) = 0; virtual FileAccess *get_file(const String &p_path, PackedData::PackedFile *p_file) = 0; virtual ~PackSource() {} }; class PackedSourcePCK : public PackSource { public: - virtual bool try_open_pack(const String &p_path, bool p_replace_files, size_t p_offset); + virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset); virtual FileAccess *get_file(const String &p_path, PackedData::PackedFile *p_file); }; class FileAccessPack : public FileAccess { PackedData::PackedFile pf; - mutable size_t pos; + mutable uint64_t pos; mutable bool eof; uint64_t off; @@ -156,25 +160,25 @@ public: virtual void close(); virtual bool is_open() const; - virtual void seek(size_t p_position); + virtual void seek(uint64_t p_position); virtual void seek_end(int64_t p_position = 0); - virtual size_t get_position() const; - virtual size_t get_len() const; + virtual uint64_t get_position() const; + virtual uint64_t get_length() const; virtual bool eof_reached() const; virtual uint8_t get_8() const; - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; - virtual void set_endian_swap(bool p_swap); + virtual void set_big_endian(bool p_big_endian); virtual Error get_error() const; virtual void flush(); virtual void store_8(uint8_t p_dest); - virtual void store_buffer(const uint8_t *p_src, int p_length); + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); virtual bool file_exists(const String &p_name); @@ -199,6 +203,16 @@ bool PackedData::has_path(const String &p_path) { return files.has(PathMD5(p_path.md5_buffer())); } +bool PackedData::has_directory(const String &p_path) { + DirAccess *da = try_open_directory(p_path); + if (da) { + memdelete(da); + return true; + } else { + return false; + } +} + class DirAccessPack : public DirAccess { PackedData::PackedDir *current; @@ -206,6 +220,8 @@ class DirAccessPack : public DirAccess { List<String> list_files; bool cdir = false; + PackedData::PackedDir *_find_dir(String p_dir); + public: virtual Error list_dir_begin(); virtual String get_next(); @@ -227,7 +243,11 @@ public: virtual Error rename(String p_from, String p_to); virtual Error remove(String p_name); - size_t get_space_left(); + uint64_t get_space_left(); + + virtual bool is_link(String p_file) { return false; } + virtual String read_link(String p_file) { return p_file; } + virtual Error create_link(String p_source, String p_target) { return FAILED; } virtual String get_filesystem_type() const; @@ -235,4 +255,13 @@ public: ~DirAccessPack() {} }; +DirAccess *PackedData::try_open_directory(const String &p_path) { + DirAccess *da = memnew(DirAccessPack()); + if (da->change_dir(p_path) != OK) { + memdelete(da); + da = nullptr; + } + return da; +} + #endif // FILE_ACCESS_PACK_H diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index ce402fe8ed..b5c882e9ce 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,8 +32,7 @@ #include "file_access_zip.h" -#include "core/os/copymem.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" ZipArchive *ZipArchive::instance = nullptr; @@ -44,14 +43,14 @@ static void *godot_open(void *data, const char *p_fname, int mode) { return nullptr; } - FileAccess *f = (FileAccess *)data; - f->open(p_fname, FileAccess::READ); + FileAccess *f = FileAccess::open(p_fname, FileAccess::READ); + ERR_FAIL_COND_V(!f, nullptr); - return f->is_open() ? data : nullptr; + return f; } static uLong godot_read(void *data, void *fdata, void *buf, uLong size) { - FileAccess *f = (FileAccess *)data; + FileAccess *f = (FileAccess *)fdata; f->get_buffer((uint8_t *)buf, size); return size; } @@ -61,20 +60,20 @@ static uLong godot_write(voidpf opaque, voidpf stream, const void *buf, uLong si } static long godot_tell(voidpf opaque, voidpf stream) { - FileAccess *f = (FileAccess *)opaque; + FileAccess *f = (FileAccess *)stream; return f->get_position(); } static long godot_seek(voidpf opaque, voidpf stream, uLong offset, int origin) { - FileAccess *f = (FileAccess *)opaque; + FileAccess *f = (FileAccess *)stream; - int pos = offset; + uint64_t pos = offset; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR: pos = f->get_position() + offset; break; case ZLIB_FILEFUNC_SEEK_END: - pos = f->get_len() + offset; + pos = f->get_length() + offset; break; default: break; @@ -85,13 +84,17 @@ static long godot_seek(voidpf opaque, voidpf stream, uLong offset, int origin) { } static int godot_close(voidpf opaque, voidpf stream) { - FileAccess *f = (FileAccess *)opaque; - f->close(); + FileAccess *f = (FileAccess *)stream; + if (f) { + f->close(); + memdelete(f); + f = nullptr; + } return 0; } static int godot_testerror(voidpf opaque, voidpf stream) { - FileAccess *f = (FileAccess *)opaque; + FileAccess *f = (FileAccess *)stream; return f->get_error() != OK ? 1 : 0; } @@ -102,28 +105,22 @@ static voidpf godot_alloc(voidpf opaque, uInt items, uInt size) { static void godot_free(voidpf opaque, voidpf address) { memfree(address); } - } // extern "C" void ZipArchive::close_handle(unzFile p_file) const { ERR_FAIL_COND_MSG(!p_file, "Cannot close a file if none is open."); - FileAccess *f = (FileAccess *)unzGetOpaque(p_file); unzCloseCurrentFile(p_file); unzClose(p_file); - memdelete(f); } unzFile ZipArchive::get_file_handle(String p_file) const { ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist."); File file = files[p_file]; - FileAccess *f = FileAccess::open(packages[file.package].filename, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, nullptr, "Cannot open file '" + packages[file.package].filename + "'."); - zlib_filefunc_def io; - zeromem(&io, sizeof(io)); + memset(&io, 0, sizeof(io)); - io.opaque = f; + io.opaque = nullptr; io.zopen_file = godot_open; io.zread_file = godot_read; io.zwrite_file = godot_write; @@ -137,7 +134,7 @@ unzFile ZipArchive::get_file_handle(String p_file) const { io.free_mem = godot_free; unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io); - ERR_FAIL_COND_V(!pkg, nullptr); + ERR_FAIL_COND_V_MSG(!pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'."); int unz_err = unzGoToFilePos(pkg, &file.file_pos); if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) { unzClose(pkg); @@ -147,8 +144,7 @@ unzFile ZipArchive::get_file_handle(String p_file) const { return pkg; } -bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, size_t p_offset = 0) { - //printf("opening zip pack %s, %i, %i\n", p_name.utf8().get_data(), p_name.extension().nocasecmp_to("zip"), p_name.extension().nocasecmp_to("pcz")); +bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset = 0) { // load with offset feature only supported for PCK files ERR_FAIL_COND_V_MSG(p_offset != 0, false, "Invalid PCK data. Note that loading files with a non-zero offset isn't supported with ZIP archives."); @@ -157,12 +153,9 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, size_ } zlib_filefunc_def io; + memset(&io, 0, sizeof(io)); - FileAccess *fa = FileAccess::open(p_path, FileAccess::READ); - if (!fa) { - return false; - } - io.opaque = fa; + io.opaque = nullptr; io.zopen_file = godot_open; io.zread_file = godot_read; io.zwrite_file = godot_write; @@ -233,9 +226,7 @@ ZipArchive::ZipArchive() { ZipArchive::~ZipArchive() { for (int i = 0; i < packages.size(); i++) { - FileAccess *f = (FileAccess *)unzGetOpaque(packages[i].zfile); unzClose(packages[i].zfile); - memdelete(f); } packages.clear(); @@ -271,22 +262,23 @@ bool FileAccessZip::is_open() const { return zfile != nullptr; } -void FileAccessZip::seek(size_t p_position) { +void FileAccessZip::seek(uint64_t p_position) { ERR_FAIL_COND(!zfile); + unzSeekCurrentFile(zfile, p_position); } void FileAccessZip::seek_end(int64_t p_position) { ERR_FAIL_COND(!zfile); - unzSeekCurrentFile(zfile, get_len() + p_position); + unzSeekCurrentFile(zfile, get_length() + p_position); } -size_t FileAccessZip::get_position() const { +uint64_t FileAccessZip::get_position() const { ERR_FAIL_COND_V(!zfile, 0); return unztell(zfile); } -size_t FileAccessZip::get_len() const { +uint64_t FileAccessZip::get_length() const { ERR_FAIL_COND_V(!zfile, 0); return file_info.uncompressed_size; } @@ -303,15 +295,17 @@ uint8_t FileAccessZip::get_8() const { return ret; } -int FileAccessZip::get_buffer(uint8_t *p_dst, int p_length) const { +uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const { + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V(!zfile, -1); + at_eof = unzeof(zfile); if (at_eof) { return 0; } - int read = unzReadCurrentFile(zfile, p_dst, p_length); + int64_t read = unzReadCurrentFile(zfile, p_dst, p_length); ERR_FAIL_COND_V(read < 0, read); - if (read < p_length) { + if ((uint64_t)read < p_length) { at_eof = true; } return read; diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h index c251b3c424..cca14ded62 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,7 @@ #ifdef MINIZIP_ENABLED #include "core/io/file_access_pack.h" -#include "core/map.h" +#include "core/templates/map.h" #include "thirdparty/minizip/unzip.h" @@ -59,8 +59,6 @@ private: static ZipArchive *instance; - FileAccess::CreateFunc fa_create_func; - public: void close_handle(unzFile p_file) const; unzFile get_file_handle(String p_file) const; @@ -69,7 +67,7 @@ public: bool file_exists(String p_name) const; - virtual bool try_open_pack(const String &p_path, bool p_replace_files, size_t p_offset); + virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset); FileAccess *get_file(const String &p_path, PackedData::PackedFile *p_file); static ZipArchive *get_singleton(); @@ -89,20 +87,21 @@ public: virtual void close(); ///< close a file virtual bool is_open() const; ///< true when file is open - virtual void seek(size_t p_position); ///< seek to a given position + virtual void seek(uint64_t p_position); ///< seek to a given position virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file + virtual uint64_t get_position() const; ///< get position in the file + virtual uint64_t get_length() const; ///< get size of the file virtual bool eof_reached() const; ///< reading passed EOF virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; virtual Error get_error() const; ///< get last error virtual void flush(); virtual void store_8(uint8_t p_dest); ///< store a byte + virtual bool file_exists(const String &p_name); ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) { return 0; } // todo diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 46e45500bf..5c1352c1b6 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,6 @@ #include "http_client.h" -#include "core/io/stream_peer_ssl.h" -#include "core/version.h" - const char *HTTPClient::_methods[METHOD_MAX] = { "GET", "HEAD", @@ -45,670 +42,35 @@ const char *HTTPClient::_methods[METHOD_MAX] = { "PATCH" }; -#ifndef JAVASCRIPT_ENABLED -Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { - close(); - - conn_port = p_port; - conn_host = p_host; - - ssl = p_ssl; - ssl_verify_host = p_verify_host; - - String host_lower = conn_host.to_lower(); - if (host_lower.begins_with("http://")) { - conn_host = conn_host.substr(7, conn_host.length() - 7); - } else if (host_lower.begins_with("https://")) { - ssl = true; - conn_host = conn_host.substr(8, conn_host.length() - 8); - } - - ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); - - if (conn_port < 0) { - if (ssl) { - conn_port = PORT_HTTPS; - } else { - conn_port = PORT_HTTP; - } - } - - connection = tcp_connection; - - if (conn_host.is_valid_ip_address()) { - // Host contains valid IP - Error err = tcp_connection->connect_to_host(IP_Address(conn_host), p_port); - if (err) { - status = STATUS_CANT_CONNECT; - return err; - } - - status = STATUS_CONNECTING; - } else { - // Host contains hostname and needs to be resolved to IP - resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host); - status = STATUS_RESOLVING; - } - - return OK; -} - -void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) { - ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object."); - - close(); - connection = p_connection; - status = STATUS_CONNECTED; -} - -Ref<StreamPeer> HTTPClient::get_connection() const { - return connection; -} - -Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { - ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); - 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"; - if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { - // Don't append the standard ports - request += "Host: " + conn_host + "\r\n"; - } else { - request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; - } - bool add_clen = p_body.size() > 0; - bool add_uagent = true; - bool add_accept = true; - for (int i = 0; i < p_headers.size(); i++) { - request += p_headers[i] + "\r\n"; - if (add_clen && p_headers[i].findn("Content-Length:") == 0) { - add_clen = false; - } - if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { - add_uagent = false; - } - if (add_accept && p_headers[i].findn("Accept:") == 0) { - add_accept = false; - } - } - if (add_clen) { - request += "Content-Length: " + itos(p_body.size()) + "\r\n"; - // Should it add utf8 encoding? - } - if (add_uagent) { - request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; - } - if (add_accept) { - request += "Accept: */*\r\n"; - } - request += "\r\n"; - CharString cs = request.utf8(); - - Vector<uint8_t> data; - data.resize(cs.length()); - { - uint8_t *data_write = data.ptrw(); - for (int i = 0; i < cs.length(); i++) { - data_write[i] = cs[i]; - } - } - - data.append_array(p_body); - - const uint8_t *r = data.ptr(); - Error err = connection->put_data(&r[0], data.size()); - - if (err) { - close(); - status = STATUS_CONNECTION_ERROR; - return err; - } - - status = STATUS_REQUESTING; - head_request = p_method == METHOD_HEAD; - - return OK; -} - -Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { - ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); - 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"; - if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { - // Don't append the standard ports - request += "Host: " + conn_host + "\r\n"; - } else { - request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; - } - bool add_uagent = true; - bool add_accept = true; - bool add_clen = p_body.length() > 0; - for (int i = 0; i < p_headers.size(); i++) { - request += p_headers[i] + "\r\n"; - if (add_clen && p_headers[i].findn("Content-Length:") == 0) { - add_clen = false; - } - if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { - add_uagent = false; - } - if (add_accept && p_headers[i].findn("Accept:") == 0) { - add_accept = false; - } - } - if (add_clen) { - request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n"; - // Should it add utf8 encoding? - } - if (add_uagent) { - request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; +HTTPClient *HTTPClient::create() { + if (_create) { + return _create(); } - if (add_accept) { - request += "Accept: */*\r\n"; - } - request += "\r\n"; - request += p_body; - - CharString cs = request.utf8(); - Error err = connection->put_data((const uint8_t *)cs.ptr(), cs.length()); - if (err) { - close(); - status = STATUS_CONNECTION_ERROR; - return err; - } - - status = STATUS_REQUESTING; - head_request = p_method == METHOD_HEAD; - - return OK; + return nullptr; } -bool HTTPClient::has_response() const { - return response_headers.size() != 0; +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); } -bool HTTPClient::is_response_chunked() const { - return chunked; +Error HTTPClient::_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { + int size = p_body.length(); + return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)p_body.utf8().get_data() : nullptr, size); } -int HTTPClient::get_response_code() const { - return response_num; -} - -Error HTTPClient::get_response_headers(List<String> *r_response) { - if (!response_headers.size()) { - return ERR_INVALID_PARAMETER; - } - - for (int i = 0; i < response_headers.size(); i++) { - r_response->push_back(response_headers[i]); - } - - response_headers.clear(); - - return OK; -} - -void HTTPClient::close() { - if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) { - tcp_connection->disconnect_from_host(); - } - - connection.unref(); - status = STATUS_DISCONNECTED; - head_request = false; - if (resolving != IP::RESOLVER_INVALID_ID) { - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - } - - response_headers.clear(); - response_str.clear(); - body_size = -1; - body_left = 0; - chunk_left = 0; - chunk_trailer_part = false; - read_until_eof = false; - response_num = 0; - handshaking = false; -} - -Error HTTPClient::poll() { - switch (status) { - case STATUS_RESOLVING: { - ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); - - IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); - switch (rstatus) { - case IP::RESOLVER_STATUS_WAITING: - return OK; // Still resolving - - case IP::RESOLVER_STATUS_DONE: { - IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - if (err) { - status = STATUS_CANT_CONNECT; - return err; - } - - status = STATUS_CONNECTING; - } break; - case IP::RESOLVER_STATUS_NONE: - case IP::RESOLVER_STATUS_ERROR: { - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - close(); - status = STATUS_CANT_RESOLVE; - return ERR_CANT_RESOLVE; - } break; - } - } break; - case STATUS_CONNECTING: { - StreamPeerTCP::Status s = tcp_connection->get_status(); - switch (s) { - case StreamPeerTCP::STATUS_CONNECTING: { - return OK; - } break; - case StreamPeerTCP::STATUS_CONNECTED: { - if (ssl) { - Ref<StreamPeerSSL> ssl; - if (!handshaking) { - // Connect the StreamPeerSSL and start handshaking - ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); - ssl->set_blocking_handshake_enabled(false); - Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); - if (err != OK) { - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - connection = ssl; - handshaking = true; - } else { - // We are already handshaking, which means we can use your already active SSL connection - ssl = static_cast<Ref<StreamPeerSSL>>(connection); - if (ssl.is_null()) { - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - - ssl->poll(); // Try to finish the handshake - } - - if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { - // Handshake has been successful - handshaking = false; - status = STATUS_CONNECTED; - return OK; - } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { - // Handshake has failed - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - // ... we will need to poll more for handshake to finish - } else { - status = STATUS_CONNECTED; - } - return OK; - } break; - case StreamPeerTCP::STATUS_ERROR: - case StreamPeerTCP::STATUS_NONE: { - close(); - status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; - } break; - } - } break; - case STATUS_BODY: - case STATUS_CONNECTED: { - // Check if we are still connected - if (ssl) { - Ref<StreamPeerSSL> tmp = connection; - tmp->poll(); - if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) { - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - // Connection established, requests can now be made - return OK; - } break; - case STATUS_REQUESTING: { - while (true) { - uint8_t byte; - int rec = 0; - Error err = _get_http_data(&byte, 1, rec); - if (err != OK) { - close(); - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - - if (rec == 0) { - return OK; // Still requesting, keep trying! - } - - response_str.push_back(byte); - int rs = response_str.size(); - if ( - (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || - (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { - // End of response, parse. - response_str.push_back(0); - String response; - response.parse_utf8((const char *)response_str.ptr()); - Vector<String> responses = response.split("\n"); - body_size = -1; - chunked = false; - body_left = 0; - chunk_left = 0; - chunk_trailer_part = false; - read_until_eof = false; - response_str.clear(); - response_headers.clear(); - response_num = RESPONSE_OK; - - // Per the HTTP 1.1 spec, keep-alive is the default. - // Not following that specification breaks standard implementations. - // Broken web servers should be fixed. - bool keep_alive = true; - - for (int i = 0; i < responses.size(); i++) { - String header = responses[i].strip_edges(); - String s = header.to_lower(); - if (s.length() == 0) { - continue; - } - if (s.begins_with("content-length:")) { - body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); - body_left = body_size; - - } else if (s.begins_with("transfer-encoding:")) { - String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); - if (encoding == "chunked") { - chunked = true; - } - } else if (s.begins_with("connection: close")) { - keep_alive = false; - } - - if (i == 0 && responses[i].begins_with("HTTP")) { - String num = responses[i].get_slicec(' ', 1); - response_num = num.to_int(); - } else { - response_headers.push_back(header); - } - } - - // This is a HEAD request, we won't receive anything. - if (head_request) { - body_size = 0; - body_left = 0; - } - - if (body_size != -1 || chunked) { - status = STATUS_BODY; - } else if (!keep_alive) { - read_until_eof = true; - status = STATUS_BODY; - } else { - status = STATUS_CONNECTED; - } - return OK; - } - } - } break; - case STATUS_DISCONNECTED: { - return ERR_UNCONFIGURED; - } break; - case STATUS_CONNECTION_ERROR: - case STATUS_SSL_HANDSHAKE_ERROR: { - return ERR_CONNECTION_ERROR; - } break; - case STATUS_CANT_CONNECT: { - return ERR_CANT_CONNECT; - } break; - case STATUS_CANT_RESOLVE: { - return ERR_CANT_RESOLVE; - } break; - } - - return OK; -} - -int HTTPClient::get_response_body_length() const { - return body_size; -} - -PackedByteArray HTTPClient::read_response_body_chunk() { - ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); - - PackedByteArray ret; - Error err = OK; - - if (chunked) { - while (true) { - if (chunk_trailer_part) { - // We need to consume the trailer part too or keep-alive will break - uint8_t b; - int rec = 0; - err = _get_http_data(&b, 1, rec); - - if (rec == 0) { - break; - } - - chunk.push_back(b); - int cs = chunk.size(); - if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { - if (cs == 2) { - // Finally over - chunk_trailer_part = false; - status = STATUS_CONNECTED; - chunk.clear(); - break; - } else { - // We do not process nor return the trailer data - chunk.clear(); - } - } - } else if (chunk_left == 0) { - // Reading length - uint8_t b; - int rec = 0; - err = _get_http_data(&b, 1, rec); - - if (rec == 0) { - break; - } - - chunk.push_back(b); - - if (chunk.size() > 32) { - ERR_PRINT("HTTP Invalid chunk hex len"); - status = STATUS_CONNECTION_ERROR; - break; - } - - if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { - int len = 0; - for (int i = 0; i < chunk.size() - 2; i++) { - char c = chunk[i]; - int v = 0; - if (c >= '0' && c <= '9') { - v = c - '0'; - } else if (c >= 'a' && c <= 'f') { - v = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - v = c - 'A' + 10; - } else { - ERR_PRINT("HTTP Chunk len not in hex!!"); - status = STATUS_CONNECTION_ERROR; - break; - } - len <<= 4; - len |= v; - if (len > (1 << 24)) { - ERR_PRINT("HTTP Chunk too big!! >16mb"); - status = STATUS_CONNECTION_ERROR; - break; - } - } - - if (len == 0) { - // End reached! - chunk_trailer_part = true; - chunk.clear(); - break; - } - - chunk_left = len + 2; - chunk.resize(chunk_left); - } - } else { - int rec = 0; - err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); - if (rec == 0) { - break; - } - chunk_left -= rec; - - if (chunk_left == 0) { - if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { - ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)"); - status = STATUS_CONNECTION_ERROR; - break; - } - - ret.resize(chunk.size() - 2); - uint8_t *w = ret.ptrw(); - copymem(w, chunk.ptr(), chunk.size() - 2); - chunk.clear(); - } - - break; - } - } - - } else { - int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; - ret.resize(to_read); - int _offset = 0; - while (to_read > 0) { - int rec = 0; - { - uint8_t *w = ret.ptrw(); - err = _get_http_data(w + _offset, to_read, rec); - } - if (rec <= 0) { // Ended up reading less - ret.resize(_offset); - break; - } else { - _offset += rec; - to_read -= rec; - if (!read_until_eof) { - body_left -= rec; - } - } - if (err != OK) { - break; - } - } - } - - if (err != OK) { - close(); - - if (err == ERR_FILE_EOF) { - status = STATUS_DISCONNECTED; // Server disconnected - } else { - status = STATUS_CONNECTION_ERROR; - } - } else if (body_left == 0 && !chunked && !read_until_eof) { - status = STATUS_CONNECTED; - } - - return ret; -} - -HTTPClient::Status HTTPClient::get_status() const { - return status; -} - -void HTTPClient::set_blocking_mode(bool p_enable) { - blocking = p_enable; -} - -bool HTTPClient::is_blocking_mode_enabled() const { - return blocking; -} - -Error HTTPClient::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) { - if (blocking) { - // We can't use StreamPeer.get_data, since when reaching EOF we will get an - // error without knowing how many bytes we received. - Error err = ERR_FILE_EOF; - int read = 0; - int left = p_bytes; - r_received = 0; - while (left > 0) { - err = connection->get_partial_data(p_buffer + r_received, left, read); - if (err == OK) { - r_received += read; - } else if (err == ERR_FILE_EOF) { - r_received += read; - return err; - } else { - return err; - } - left -= read; - } - return err; - } else { - return connection->get_partial_data(p_buffer, p_bytes, r_received); - } -} - -void HTTPClient::set_read_chunk_size(int p_size) { - ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); - read_chunk_size = p_size; -} - -int HTTPClient::get_read_chunk_size() const { - return read_chunk_size; -} - -HTTPClient::HTTPClient() { - tcp_connection.instance(); -} - -HTTPClient::~HTTPClient() {} - -#endif // #ifndef JAVASCRIPT_ENABLED - String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { String query = ""; Array keys = p_dict.keys(); for (int i = 0; i < keys.size(); ++i) { - String encoded_key = String(keys[i]).http_escape(); + String encoded_key = String(keys[i]).uri_encode(); Variant value = p_dict[keys[i]]; switch (value.get_type()) { case Variant::ARRAY: { // Repeat the key with every values Array values = value; for (int j = 0; j < values.size(); ++j) { - query += "&" + encoded_key + "=" + String(values[j]).http_escape(); + query += "&" + encoded_key + "=" + String(values[j]).uri_encode(); } break; } @@ -719,7 +81,7 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { } default: { // Add the key-value pair - query += "&" + encoded_key + "=" + String(value).http_escape(); + query += "&" + encoded_key + "=" + String(value).uri_encode(); } } } @@ -731,8 +93,7 @@ Dictionary HTTPClient::_get_response_headers_as_dictionary() { List<String> rh; get_response_headers(&rh); Dictionary ret; - for (const List<String>::Element *E = rh.front(); E; E = E->next()) { - const String &s = E->get(); + for (const String &s : rh) { int sp = s.find(":"); if (sp == -1) { continue; @@ -751,8 +112,8 @@ PackedStringArray HTTPClient::_get_response_headers() { PackedStringArray ret; ret.resize(rh.size()); int idx = 0; - for (const List<String>::Element *E = rh.front(); E; E = E->next()) { - ret.set(idx++, E->get()); + for (const String &E : rh) { + ret.set(idx++, E); } return ret; @@ -762,8 +123,8 @@ void HTTPClient::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_ssl", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection); ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection); - ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::request_raw); - ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::request, DEFVAL(String())); + ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw); + ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::_request, DEFVAL(String())); ClassDB::bind_method(D_METHOD("close"), &HTTPClient::close); ClassDB::bind_method(D_METHOD("has_response"), &HTTPClient::has_response); @@ -785,7 +146,7 @@ void HTTPClient::_bind_methods() { 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"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "connection", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", 0), "set_connection", "get_connection"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "connection", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", PROPERTY_USAGE_NONE), "set_connection", "get_connection"); ADD_PROPERTY(PropertyInfo(Variant::INT, "read_chunk_size", PROPERTY_HINT_RANGE, "256,16777216"), "set_read_chunk_size", "get_read_chunk_size"); BIND_ENUM_CONSTANT(METHOD_GET); diff --git a/core/io/http_client.h b/core/io/http_client.h index 1dc1f3d76a..718c3a905e 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,14 +34,13 @@ #include "core/io/ip.h" #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" -#include "core/reference.h" +#include "core/object/ref_counted.h" -class HTTPClient : public Reference { - GDCLASS(HTTPClient, Reference); +class HTTPClient : public RefCounted { + GDCLASS(HTTPClient, RefCounted); public: enum ResponseCode { - // 1xx informational RESPONSE_CONTINUE = 100, RESPONSE_SWITCHING_PROTOCOLS = 101, @@ -116,7 +115,6 @@ public: }; enum Method { - METHOD_GET, METHOD_HEAD, METHOD_POST, @@ -131,7 +129,6 @@ public: }; enum Status { - STATUS_DISCONNECTED, STATUS_RESOLVING, // Resolving hostname (if passed a hostname) STATUS_CANT_RESOLVE, @@ -145,89 +142,58 @@ public: }; -private: +protected: static const char *_methods[METHOD_MAX]; static const int HOST_MIN_LEN = 4; enum Port { - PORT_HTTP = 80, PORT_HTTPS = 443, }; -#ifndef JAVASCRIPT_ENABLED - Status status = STATUS_DISCONNECTED; - IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; - int conn_port = -1; - String conn_host; - bool ssl = false; - bool ssl_verify_host = false; - bool blocking = false; - bool handshaking = false; - bool head_request = false; - - Vector<uint8_t> response_str; - - bool chunked = false; - Vector<uint8_t> chunk; - int chunk_left = 0; - bool chunk_trailer_part = false; - int body_size = -1; - int body_left = 0; - bool read_until_eof = false; - - Ref<StreamPeerTCP> tcp_connection; - Ref<StreamPeer> connection; - - int response_num = 0; - Vector<String> response_headers; - int read_chunk_size = 4096; - - Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); - -#else -#include "platform/javascript/http_client.h.inc" -#endif - PackedStringArray _get_response_headers(); Dictionary _get_response_headers_as_dictionary(); + Error _request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body); + Error _request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String()); + + static HTTPClient *(*_create)(); static void _bind_methods(); public: - Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true); + static HTTPClient *create(); - void set_connection(const Ref<StreamPeer> &p_connection); - Ref<StreamPeer> get_connection() const; + String query_string_from_dict(const Dictionary &p_dict); - Error request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body); - Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String()); + virtual Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) = 0; + virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) = 0; - void close(); + virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0; + virtual Ref<StreamPeer> get_connection() const = 0; - Status get_status() const; + virtual void close() = 0; - bool has_response() const; - bool is_response_chunked() const; - int get_response_code() const; - Error get_response_headers(List<String> *r_response); - int get_response_body_length() const; + virtual Status get_status() const = 0; - PackedByteArray read_response_body_chunk(); // Can't get body as partial text because of most encodings UTF8, gzip, etc. + virtual bool has_response() const = 0; + virtual bool is_response_chunked() const = 0; + virtual int get_response_code() const = 0; + virtual Error get_response_headers(List<String> *r_response) = 0; + virtual int get_response_body_length() const = 0; - void set_blocking_mode(bool p_enable); // Useful mostly if running in a thread - bool is_blocking_mode_enabled() const; + virtual PackedByteArray read_response_body_chunk() = 0; // Can't get body as partial text because of most encodings UTF8, gzip, etc. - void set_read_chunk_size(int p_size); - int get_read_chunk_size() const; + virtual void set_blocking_mode(bool p_enable) = 0; // Useful mostly if running in a thread + virtual bool is_blocking_mode_enabled() const = 0; - Error poll(); + virtual void set_read_chunk_size(int p_size) = 0; + virtual int get_read_chunk_size() const = 0; - String query_string_from_dict(const Dictionary &p_dict); + virtual Error poll() = 0; - HTTPClient(); - ~HTTPClient(); + HTTPClient() {} + virtual ~HTTPClient() {} }; VARIANT_ENUM_CAST(HTTPClient::ResponseCode) diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp new file mode 100644 index 0000000000..b3d35b3603 --- /dev/null +++ b/core/io/http_client_tcp.cpp @@ -0,0 +1,687 @@ +/*************************************************************************/ +/* http_client_tcp.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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "http_client_tcp.h" + +#include "core/io/stream_peer_ssl.h" +#include "core/version.h" + +HTTPClient *HTTPClientTCP::_create_func() { + return memnew(HTTPClientTCP); +} + +Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { + close(); + + conn_port = p_port; + conn_host = p_host; + + ip_candidates.clear(); + + ssl = p_ssl; + ssl_verify_host = p_verify_host; + + String host_lower = conn_host.to_lower(); + if (host_lower.begins_with("http://")) { + conn_host = conn_host.substr(7, conn_host.length() - 7); + } else if (host_lower.begins_with("https://")) { + ssl = true; + conn_host = conn_host.substr(8, conn_host.length() - 8); + } + + ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); + + if (conn_port < 0) { + if (ssl) { + conn_port = PORT_HTTPS; + } else { + conn_port = PORT_HTTP; + } + } + + connection = tcp_connection; + + if (conn_host.is_valid_ip_address()) { + // Host contains valid IP + Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port); + if (err) { + status = STATUS_CANT_CONNECT; + return err; + } + + status = STATUS_CONNECTING; + } else { + // Host contains hostname and needs to be resolved to IP + resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host); + status = STATUS_RESOLVING; + } + + return OK; +} + +void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) { + ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object."); + + if (ssl) { + ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerSSL>(p_connection.ptr()), + "Connection is not a reference to a valid StreamPeerSSL object."); + } + + if (connection == p_connection) { + return; + } + + close(); + connection = p_connection; + status = STATUS_CONNECTED; +} + +Ref<StreamPeer> HTTPClientTCP::get_connection() const { + return connection; +} + +static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) { + switch (p_method) { + case HTTPClientTCP::METHOD_CONNECT: { + // Authority in host:port format, as in RFC7231 + int pos = p_url.find_char(':'); + return 0 < pos && pos < p_url.length() - 1; + } + case HTTPClientTCP::METHOD_OPTIONS: { + if (p_url == "*") { + return true; + } + [[fallthrough]]; + } + default: + // Absolute path or absolute URL + return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://"); + } +} + +Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) { + ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER); + 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"; + bool add_host = true; + bool add_clen = p_body_size > 0; + bool add_uagent = true; + bool add_accept = true; + for (int i = 0; i < p_headers.size(); i++) { + request += p_headers[i] + "\r\n"; + if (add_host && p_headers[i].findn("Host:") == 0) { + add_host = false; + } + if (add_clen && p_headers[i].findn("Content-Length:") == 0) { + add_clen = false; + } + if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { + add_uagent = false; + } + if (add_accept && p_headers[i].findn("Accept:") == 0) { + add_accept = false; + } + } + if (add_host) { + if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { + // Don't append the standard ports + request += "Host: " + conn_host + "\r\n"; + } else { + request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; + } + } + if (add_clen) { + request += "Content-Length: " + itos(p_body_size) + "\r\n"; + // Should it add utf8 encoding? + } + if (add_uagent) { + request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; + } + if (add_accept) { + request += "Accept: */*\r\n"; + } + request += "\r\n"; + CharString cs = request.utf8(); + + Vector<uint8_t> data; + data.resize(cs.length() + p_body_size); + memcpy(data.ptrw(), cs.get_data(), cs.length()); + if (p_body_size > 0) { + memcpy(data.ptrw() + cs.length(), p_body, p_body_size); + } + + // TODO Implement non-blocking requests. + Error err = connection->put_data(data.ptr(), data.size()); + + if (err) { + close(); + status = STATUS_CONNECTION_ERROR; + return err; + } + + status = STATUS_REQUESTING; + head_request = p_method == METHOD_HEAD; + + return OK; +} + +bool HTTPClientTCP::has_response() const { + return response_headers.size() != 0; +} + +bool HTTPClientTCP::is_response_chunked() const { + return chunked; +} + +int HTTPClientTCP::get_response_code() const { + return response_num; +} + +Error HTTPClientTCP::get_response_headers(List<String> *r_response) { + if (!response_headers.size()) { + return ERR_INVALID_PARAMETER; + } + + for (int i = 0; i < response_headers.size(); i++) { + r_response->push_back(response_headers[i]); + } + + response_headers.clear(); + + return OK; +} + +void HTTPClientTCP::close() { + if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) { + tcp_connection->disconnect_from_host(); + } + + connection.unref(); + status = STATUS_DISCONNECTED; + head_request = false; + if (resolving != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + } + + ip_candidates.clear(); + response_headers.clear(); + response_str.clear(); + body_size = -1; + body_left = 0; + chunk_left = 0; + chunk_trailer_part = false; + read_until_eof = false; + response_num = 0; + handshaking = false; +} + +Error HTTPClientTCP::poll() { + switch (status) { + case STATUS_RESOLVING: { + ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); + + IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); + switch (rstatus) { + case IP::RESOLVER_STATUS_WAITING: + return OK; // Still resolving + + case IP::RESOLVER_STATUS_DONE: { + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + + 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); + if (err == OK) { + break; + } + } + if (err) { + status = STATUS_CANT_CONNECT; + return err; + } + + status = STATUS_CONNECTING; + } break; + case IP::RESOLVER_STATUS_NONE: + case IP::RESOLVER_STATUS_ERROR: { + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + close(); + status = STATUS_CANT_RESOLVE; + return ERR_CANT_RESOLVE; + } break; + } + } break; + case STATUS_CONNECTING: { + StreamPeerTCP::Status s = tcp_connection->get_status(); + switch (s) { + case StreamPeerTCP::STATUS_CONNECTING: { + return OK; + } break; + case StreamPeerTCP::STATUS_CONNECTED: { + if (ssl) { + Ref<StreamPeerSSL> ssl; + if (!handshaking) { + // Connect the StreamPeerSSL and start handshaking + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ssl->set_blocking_handshake_enabled(false); + Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); + if (err != OK) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + connection = ssl; + handshaking = true; + } else { + // We are already handshaking, which means we can use your already active SSL connection + ssl = static_cast<Ref<StreamPeerSSL>>(connection); + if (ssl.is_null()) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + + ssl->poll(); // Try to finish the handshake + } + + if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { + // Handshake has been successful + handshaking = false; + ip_candidates.clear(); + status = STATUS_CONNECTED; + return OK; + } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { + // Handshake has failed + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + // ... we will need to poll more for handshake to finish + } else { + ip_candidates.clear(); + status = STATUS_CONNECTED; + } + return OK; + } break; + case StreamPeerTCP::STATUS_ERROR: + case StreamPeerTCP::STATUS_NONE: { + 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); + if (err == OK) { + return OK; + } + } + close(); + status = STATUS_CANT_CONNECT; + return err; + } break; + } + } break; + case STATUS_BODY: + case STATUS_CONNECTED: { + // Check if we are still connected + if (ssl) { + Ref<StreamPeerSSL> tmp = connection; + tmp->poll(); + if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + // Connection established, requests can now be made + return OK; + } break; + case STATUS_REQUESTING: { + while (true) { + uint8_t byte; + int rec = 0; + Error err = _get_http_data(&byte, 1, rec); + if (err != OK) { + close(); + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + + if (rec == 0) { + return OK; // Still requesting, keep trying! + } + + response_str.push_back(byte); + int rs = response_str.size(); + if ( + (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || + (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { + // End of response, parse. + response_str.push_back(0); + String response; + response.parse_utf8((const char *)response_str.ptr()); + Vector<String> responses = response.split("\n"); + body_size = -1; + chunked = false; + body_left = 0; + chunk_left = 0; + chunk_trailer_part = false; + read_until_eof = false; + response_str.clear(); + response_headers.clear(); + response_num = RESPONSE_OK; + + // Per the HTTP 1.1 spec, keep-alive is the default. + // Not following that specification breaks standard implementations. + // Broken web servers should be fixed. + bool keep_alive = true; + + for (int i = 0; i < responses.size(); i++) { + String header = responses[i].strip_edges(); + String s = header.to_lower(); + if (s.length() == 0) { + continue; + } + if (s.begins_with("content-length:")) { + body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); + body_left = body_size; + + } else if (s.begins_with("transfer-encoding:")) { + String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); + if (encoding == "chunked") { + chunked = true; + } + } else if (s.begins_with("connection: close")) { + keep_alive = false; + } + + if (i == 0 && responses[i].begins_with("HTTP")) { + String num = responses[i].get_slicec(' ', 1); + response_num = num.to_int(); + } else { + response_headers.push_back(header); + } + } + + // This is a HEAD request, we won't receive anything. + if (head_request) { + body_size = 0; + body_left = 0; + } + + if (body_size != -1 || chunked) { + status = STATUS_BODY; + } else if (!keep_alive) { + read_until_eof = true; + status = STATUS_BODY; + } else { + status = STATUS_CONNECTED; + } + return OK; + } + } + } break; + case STATUS_DISCONNECTED: { + return ERR_UNCONFIGURED; + } break; + case STATUS_CONNECTION_ERROR: + case STATUS_SSL_HANDSHAKE_ERROR: { + return ERR_CONNECTION_ERROR; + } break; + case STATUS_CANT_CONNECT: { + return ERR_CANT_CONNECT; + } break; + case STATUS_CANT_RESOLVE: { + return ERR_CANT_RESOLVE; + } break; + } + + return OK; +} + +int HTTPClientTCP::get_response_body_length() const { + return body_size; +} + +PackedByteArray HTTPClientTCP::read_response_body_chunk() { + ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); + + PackedByteArray ret; + Error err = OK; + + if (chunked) { + while (true) { + if (chunk_trailer_part) { + // We need to consume the trailer part too or keep-alive will break + uint8_t b; + int rec = 0; + err = _get_http_data(&b, 1, rec); + + if (rec == 0) { + break; + } + + chunk.push_back(b); + int cs = chunk.size(); + if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { + if (cs == 2) { + // Finally over + chunk_trailer_part = false; + status = STATUS_CONNECTED; + chunk.clear(); + break; + } else { + // We do not process nor return the trailer data + chunk.clear(); + } + } + } else if (chunk_left == 0) { + // Reading length + uint8_t b; + int rec = 0; + err = _get_http_data(&b, 1, rec); + + if (rec == 0) { + break; + } + + chunk.push_back(b); + + if (chunk.size() > 32) { + ERR_PRINT("HTTP Invalid chunk hex len"); + status = STATUS_CONNECTION_ERROR; + break; + } + + if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { + int len = 0; + for (int i = 0; i < chunk.size() - 2; i++) { + char c = chunk[i]; + int v = 0; + if (c >= '0' && c <= '9') { + v = c - '0'; + } else if (c >= 'a' && c <= 'f') { + v = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + v = c - 'A' + 10; + } else { + ERR_PRINT("HTTP Chunk len not in hex!!"); + status = STATUS_CONNECTION_ERROR; + break; + } + len <<= 4; + len |= v; + if (len > (1 << 24)) { + ERR_PRINT("HTTP Chunk too big!! >16mb"); + status = STATUS_CONNECTION_ERROR; + break; + } + } + + if (len == 0) { + // End reached! + chunk_trailer_part = true; + chunk.clear(); + break; + } + + chunk_left = len + 2; + chunk.resize(chunk_left); + } + } else { + int rec = 0; + err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); + if (rec == 0) { + break; + } + chunk_left -= rec; + + if (chunk_left == 0) { + if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { + ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)"); + status = STATUS_CONNECTION_ERROR; + break; + } + + ret.resize(chunk.size() - 2); + uint8_t *w = ret.ptrw(); + memcpy(w, chunk.ptr(), chunk.size() - 2); + chunk.clear(); + } + + break; + } + } + + } else { + int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; + ret.resize(to_read); + int _offset = 0; + while (to_read > 0) { + int rec = 0; + { + uint8_t *w = ret.ptrw(); + err = _get_http_data(w + _offset, to_read, rec); + } + if (rec <= 0) { // Ended up reading less + ret.resize(_offset); + break; + } else { + _offset += rec; + to_read -= rec; + if (!read_until_eof) { + body_left -= rec; + } + } + if (err != OK) { + ret.resize(_offset); + break; + } + } + } + + if (err != OK) { + close(); + + if (err == ERR_FILE_EOF) { + status = STATUS_DISCONNECTED; // Server disconnected + } else { + status = STATUS_CONNECTION_ERROR; + } + } else if (body_left == 0 && !chunked && !read_until_eof) { + status = STATUS_CONNECTED; + } + + return ret; +} + +HTTPClientTCP::Status HTTPClientTCP::get_status() const { + return status; +} + +void HTTPClientTCP::set_blocking_mode(bool p_enable) { + blocking = p_enable; +} + +bool HTTPClientTCP::is_blocking_mode_enabled() const { + return blocking; +} + +Error HTTPClientTCP::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) { + if (blocking) { + // We can't use StreamPeer.get_data, since when reaching EOF we will get an + // error without knowing how many bytes we received. + Error err = ERR_FILE_EOF; + int read = 0; + int left = p_bytes; + r_received = 0; + while (left > 0) { + err = connection->get_partial_data(p_buffer + r_received, left, read); + if (err == OK) { + r_received += read; + } else if (err == ERR_FILE_EOF) { + r_received += read; + return err; + } else { + return err; + } + left -= read; + } + return err; + } else { + return connection->get_partial_data(p_buffer, p_bytes, r_received); + } +} + +void HTTPClientTCP::set_read_chunk_size(int p_size) { + ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); + read_chunk_size = p_size; +} + +int HTTPClientTCP::get_read_chunk_size() const { + return read_chunk_size; +} + +HTTPClientTCP::HTTPClientTCP() { + tcp_connection.instantiate(); +} + +HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; + +#endif // #ifndef JAVASCRIPT_ENABLED diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h new file mode 100644 index 0000000000..170afb551c --- /dev/null +++ b/core/io/http_client_tcp.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* http_client_tcp.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 HTTP_CLIENT_TCP_H +#define HTTP_CLIENT_TCP_H + +#include "http_client.h" + +class HTTPClientTCP : public HTTPClient { +private: + Status status = STATUS_DISCONNECTED; + IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; + Array ip_candidates; + int conn_port = -1; + String conn_host; + bool ssl = false; + bool ssl_verify_host = false; + bool blocking = false; + bool handshaking = false; + bool head_request = false; + + Vector<uint8_t> response_str; + + bool chunked = false; + Vector<uint8_t> chunk; + int chunk_left = 0; + bool chunk_trailer_part = false; + int body_size = -1; + int body_left = 0; + bool read_until_eof = false; + + Ref<StreamPeerTCP> tcp_connection; + Ref<StreamPeer> connection; + + int response_num = 0; + Vector<String> response_headers; + // 64 KiB by default (favors fast download speeds at the cost of memory usage). + int read_chunk_size = 65536; + + Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); + +public: + static HTTPClient *_create_func(); + + Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override; + + Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) override; + void set_connection(const Ref<StreamPeer> &p_connection) override; + Ref<StreamPeer> get_connection() const override; + void close() override; + Status get_status() const override; + bool has_response() const override; + bool is_response_chunked() const override; + int get_response_code() const override; + Error get_response_headers(List<String> *r_response) override; + int get_response_body_length() const override; + PackedByteArray read_response_body_chunk() override; + void set_blocking_mode(bool p_enable) override; + bool is_blocking_mode_enabled() const override; + void set_read_chunk_size(int p_size) override; + int get_read_chunk_size() const override; + Error poll() override; + HTTPClientTCP(); +}; + +#endif // HTTP_CLIENT_TCP_H diff --git a/core/io/image.cpp b/core/io/image.cpp new file mode 100644 index 0000000000..3112dd217f --- /dev/null +++ b/core/io/image.cpp @@ -0,0 +1,3646 @@ +/*************************************************************************/ +/* image.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 "image.h" + +#include "core/error/error_macros.h" +#include "core/io/image_loader.h" +#include "core/io/resource_loader.h" +#include "core/math/math_funcs.h" +#include "core/string/print_string.h" +#include "core/templates/hash_map.h" + +#include <stdio.h> + +const char *Image::format_names[Image::FORMAT_MAX] = { + "Lum8", //luminance + "LumAlpha8", //luminance-alpha + "Red8", + "RedGreen", + "RGB8", + "RGBA8", + "RGBA4444", + "RGBA5551", + "RFloat", //float + "RGFloat", + "RGBFloat", + "RGBAFloat", + "RHalf", //half float + "RGHalf", + "RGBHalf", + "RGBAHalf", + "RGBE9995", + "DXT1 RGB8", //s3tc + "DXT3 RGBA8", + "DXT5 RGBA8", + "RGTC Red8", + "RGTC RedGreen8", + "BPTC_RGBA", + "BPTC_RGBF", + "BPTC_RGBFU", + "PVRTC1_2", //pvrtc + "PVRTC1_2A", + "PVRTC1_4", + "PVRTC1_4A", + "ETC", //etc1 + "ETC2_R11", //etc2 + "ETC2_R11S", //signed", NOT srgb. + "ETC2_RG11", + "ETC2_RG11S", + "ETC2_RGB8", + "ETC2_RGBA8", + "ETC2_RGB8A1", + "ETC2_RA_AS_RG", + "FORMAT_DXT5_RA_AS_RG", +}; + +SavePNGFunc Image::save_png_func = nullptr; +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::_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]; + } +} + +int Image::get_format_pixel_size(Format p_format) { + switch (p_format) { + case FORMAT_L8: + return 1; //luminance + case FORMAT_LA8: + return 2; //luminance-alpha + case FORMAT_R8: + return 1; + case FORMAT_RG8: + return 2; + case FORMAT_RGB8: + return 3; + case FORMAT_RGBA8: + return 4; + case FORMAT_RGBA4444: + return 2; + case FORMAT_RGB565: + return 2; + case FORMAT_RF: + return 4; //float + case FORMAT_RGF: + return 8; + case FORMAT_RGBF: + return 12; + case FORMAT_RGBAF: + return 16; + case FORMAT_RH: + return 2; //half float + case FORMAT_RGH: + return 4; + case FORMAT_RGBH: + return 6; + case FORMAT_RGBAH: + return 8; + case FORMAT_RGBE9995: + return 4; + case FORMAT_DXT1: + return 1; //s3tc bc1 + case FORMAT_DXT3: + return 1; //bc2 + case FORMAT_DXT5: + return 1; //bc3 + case FORMAT_RGTC_R: + return 1; //bc4 + case FORMAT_RGTC_RG: + return 1; //bc5 + case FORMAT_BPTC_RGBA: + return 1; //btpc bc6h + case FORMAT_BPTC_RGBF: + return 1; //float / + case FORMAT_BPTC_RGBFU: + return 1; //unsigned float + case FORMAT_PVRTC1_2: + return 1; //pvrtc + case FORMAT_PVRTC1_2A: + return 1; + case FORMAT_PVRTC1_4: + return 1; + case FORMAT_PVRTC1_4A: + return 1; + case FORMAT_ETC: + return 1; //etc1 + case FORMAT_ETC2_R11: + return 1; //etc2 + case FORMAT_ETC2_R11S: + return 1; //signed: return 1; NOT srgb. + case FORMAT_ETC2_RG11: + return 1; + case FORMAT_ETC2_RG11S: + return 1; + case FORMAT_ETC2_RGB8: + return 1; + case FORMAT_ETC2_RGBA8: + return 1; + case FORMAT_ETC2_RGB8A1: + return 1; + case FORMAT_ETC2_RA_AS_RG: + return 1; + case FORMAT_DXT5_RA_AS_RG: + return 1; + case FORMAT_MAX: { + } + } + return 0; +} + +void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) { + switch (p_format) { + case FORMAT_DXT1: //s3tc bc1 + case FORMAT_DXT3: //bc2 + case FORMAT_DXT5: //bc3 + case FORMAT_RGTC_R: //bc4 + case FORMAT_RGTC_RG: { //bc5 case case FORMAT_DXT1: + + r_w = 4; + r_h = 4; + } break; + case FORMAT_PVRTC1_2: + case FORMAT_PVRTC1_2A: { + r_w = 16; + r_h = 8; + } break; + case FORMAT_PVRTC1_4A: + case FORMAT_PVRTC1_4: { + r_w = 8; + r_h = 8; + } break; + case FORMAT_ETC: { + r_w = 4; + r_h = 4; + } break; + case FORMAT_BPTC_RGBA: + case FORMAT_BPTC_RGBF: + case FORMAT_BPTC_RGBFU: { + r_w = 4; + r_h = 4; + } break; + case FORMAT_ETC2_R11: //etc2 + case FORMAT_ETC2_R11S: //signed: NOT srgb. + case FORMAT_ETC2_RG11: + case FORMAT_ETC2_RG11S: + case FORMAT_ETC2_RGB8: + case FORMAT_ETC2_RGBA8: + case FORMAT_ETC2_RGB8A1: + case FORMAT_ETC2_RA_AS_RG: + case FORMAT_DXT5_RA_AS_RG: { + r_w = 4; + r_h = 4; + + } break; + + default: { + r_w = 1; + r_h = 1; + } break; + } +} + +int Image::get_format_pixel_rshift(Format p_format) { + if (p_format == FORMAT_DXT1 || p_format == FORMAT_RGTC_R || p_format == FORMAT_PVRTC1_4 || p_format == FORMAT_PVRTC1_4A || p_format == FORMAT_ETC || p_format == FORMAT_ETC2_R11 || p_format == FORMAT_ETC2_R11S || p_format == FORMAT_ETC2_RGB8 || p_format == FORMAT_ETC2_RGB8A1) { + return 1; + } else if (p_format == FORMAT_PVRTC1_2 || p_format == FORMAT_PVRTC1_2A) { + return 2; + } else { + return 0; + } +} + +int Image::get_format_block_size(Format p_format) { + switch (p_format) { + case FORMAT_DXT1: //s3tc bc1 + case FORMAT_DXT3: //bc2 + case FORMAT_DXT5: //bc3 + case FORMAT_RGTC_R: //bc4 + case FORMAT_RGTC_RG: { //bc5 case case FORMAT_DXT1: + + return 4; + } + case FORMAT_PVRTC1_2: + case FORMAT_PVRTC1_2A: { + return 4; + } + case FORMAT_PVRTC1_4A: + case FORMAT_PVRTC1_4: { + return 4; + } + case FORMAT_ETC: { + return 4; + } + case FORMAT_BPTC_RGBA: + case FORMAT_BPTC_RGBF: + case FORMAT_BPTC_RGBFU: { + return 4; + } + case FORMAT_ETC2_R11: //etc2 + case FORMAT_ETC2_R11S: //signed: NOT srgb. + case FORMAT_ETC2_RG11: + case FORMAT_ETC2_RG11S: + case FORMAT_ETC2_RGB8: + case FORMAT_ETC2_RGBA8: + case FORMAT_ETC2_RGB8A1: + case FORMAT_ETC2_RA_AS_RG: //used to make basis universal happy + case FORMAT_DXT5_RA_AS_RG: //used to make basis universal happy + + { + return 4; + } + default: { + } + } + + return 1; +} + +void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { + int w = width; + int h = height; + int ofs = 0; + + int pixel_size = get_format_pixel_size(format); + int pixel_rshift = get_format_pixel_rshift(format); + int block = get_format_block_size(format); + int minw, minh; + get_format_min_pixel_size(format, minw, minh); + + for (int i = 0; i < p_mipmap; i++) { + int bw = w % block != 0 ? w + (block - w % block) : w; + int bh = h % block != 0 ? h + (block - h % block) : h; + + int s = bw * bh; + + s *= pixel_size; + s >>= pixel_rshift; + ofs += s; + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } + + r_offset = ofs; + r_width = w; + r_height = h; +} + +int Image::get_mipmap_offset(int p_mipmap) const { + ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); + + int ofs, w, h; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + return ofs; +} + +int Image::get_mipmap_byte_size(int p_mipmap) const { + ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); + + int ofs, w, h; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + int ofs2; + _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); + return ofs2 - ofs; +} + +void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const { + int ofs, w, h; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + int ofs2; + _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); + r_ofs = ofs; + r_size = ofs2 - ofs; +} + +void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { + int ofs; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + int ofs2, w2, h2; + _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); + r_ofs = ofs; + r_size = ofs2 - ofs; +} + +Image::Image3DValidateError Image::validate_3d_image(Image::Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images) { + int w = p_width; + int h = p_height; + int d = p_depth; + + int arr_ofs = 0; + + while (true) { + for (int i = 0; i < d; i++) { + int idx = i + arr_ofs; + if (idx >= p_images.size()) { + return VALIDATE_3D_ERR_MISSING_IMAGES; + } + if (p_images[idx].is_null() || p_images[idx]->is_empty()) { + return VALIDATE_3D_ERR_IMAGE_EMPTY; + } + if (p_images[idx]->get_format() != p_format) { + return VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH; + } + if (p_images[idx]->get_width() != w || p_images[idx]->get_height() != h) { + return VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH; + } + if (p_images[idx]->has_mipmaps()) { + return VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS; + } + } + + arr_ofs += d; + + if (!p_mipmaps) { + break; + } + + if (w == 1 && h == 1 && d == 1) { + break; + } + + w = MAX(1, w >> 1); + h = MAX(1, h >> 1); + d = MAX(1, d >> 1); + } + + if (arr_ofs != p_images.size()) { + return VALIDATE_3D_ERR_EXTRA_IMAGES; + } + + return VALIDATE_3D_OK; +} + +String Image::get_3d_image_validation_error_text(Image3DValidateError p_error) { + switch (p_error) { + case VALIDATE_3D_OK: { + return TTR("Ok"); + } break; + case VALIDATE_3D_ERR_IMAGE_EMPTY: { + return TTR("Empty Image found"); + } break; + case VALIDATE_3D_ERR_MISSING_IMAGES: { + return TTR("Missing Images"); + } break; + case VALIDATE_3D_ERR_EXTRA_IMAGES: { + return TTR("Too many Images"); + } break; + case VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH: { + return TTR("Image size mismatch"); + } break; + case VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH: { + return TTR("Image format mismatch"); + } break; + case VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS: { + return TTR("Image has included mipmaps"); + } break; + } + return String(); +} + +int Image::get_width() const { + return width; +} + +int Image::get_height() const { + return height; +} + +Vector2 Image::get_size() const { + return Vector2(width, height); +} + +bool Image::has_mipmaps() const { + return mipmaps; +} + +int Image::get_mipmap_count() const { + if (mipmaps) { + return get_image_required_mipmaps(width, height, format); + } else { + return 0; + } +} + +//using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers +template <uint32_t read_bytes, bool read_alpha, uint32_t write_bytes, bool write_alpha, bool read_gray, bool write_gray> +static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { + uint32_t max_bytes = MAX(read_bytes, write_bytes); + + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + const uint8_t *rofs = &p_src[((y * p_width) + x) * (read_bytes + (read_alpha ? 1 : 0))]; + uint8_t *wofs = &p_dst[((y * p_width) + x) * (write_bytes + (write_alpha ? 1 : 0))]; + + uint8_t rgba[4]; + + if (read_gray) { + rgba[0] = rofs[0]; + rgba[1] = rofs[0]; + rgba[2] = rofs[0]; + } else { + for (uint32_t i = 0; i < max_bytes; i++) { + rgba[i] = (i < read_bytes) ? rofs[i] : 0; + } + } + + if (read_alpha || write_alpha) { + rgba[3] = read_alpha ? rofs[read_bytes] : 255; + } + + if (write_gray) { + //TODO: not correct grayscale, should use fixed point version of actual weights + wofs[0] = uint8_t((uint16_t(rofs[0]) + uint16_t(rofs[1]) + uint16_t(rofs[2])) / 3); + } else { + for (uint32_t i = 0; i < write_bytes; i++) { + wofs[i] = rgba[i]; + } + } + + if (write_alpha) { + wofs[write_bytes] = rgba[3]; + } + } + } +} + +void Image::convert(Format p_new_format) { + if (data.size() == 0) { + return; + } + + if (p_new_format == format) { + return; + } + + if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) { + ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); + + } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { + //use put/set pixel which is slower but works with non byte formats + Image new_img(width, height, false, p_new_format); + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + new_img.set_pixel(i, j, get_pixel(i, j)); + } + } + + if (has_mipmaps()) { + new_img.generate_mipmaps(); + } + + _copy_internals_from(new_img); + + return; + } + + Image new_img(width, height, false, p_new_format); + + const uint8_t *rptr = data.ptr(); + uint8_t *wptr = new_img.data.ptrw(); + + int conversion_type = format | p_new_format << 8; + + switch (conversion_type) { + case FORMAT_L8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, true, true>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_R8 << 8): + _convert<1, false, 1, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_L8 << 8): + _convert<1, true, 1, false, true, true>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_R8 << 8): + _convert<1, true, 1, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RG8 << 8): + _convert<1, true, 2, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGB8 << 8): + _convert<1, true, 3, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGBA8 << 8): + _convert<1, true, 3, true, true, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_L8 << 8): + _convert<1, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_L8 << 8): + _convert<2, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_LA8 << 8): + _convert<2, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_R8 << 8): + _convert<2, false, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGB8 << 8): + _convert<2, false, 3, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGBA8 << 8): + _convert<2, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_L8 << 8): + _convert<3, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_LA8 << 8): + _convert<3, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_R8 << 8): + _convert<3, false, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RG8 << 8): + _convert<3, false, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): + _convert<3, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_L8 << 8): + _convert<3, true, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_LA8 << 8): + _convert<3, true, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_R8 << 8): + _convert<3, true, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RG8 << 8): + _convert<3, true, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): + _convert<3, true, 3, false, false, false>(width, height, rptr, wptr); + break; + } + + bool gen_mipmaps = mipmaps; + + _copy_internals_from(new_img); + + if (gen_mipmaps) { + generate_mipmaps(); + } +} + +Image::Format Image::get_format() const { + return format; +} + +static double _bicubic_interp_kernel(double x) { + x = ABS(x); + + double bc = 0; + + if (x <= 1) { + bc = (1.5 * x - 2.5) * x * x + 1; + } else if (x < 2) { + bc = ((-0.5 * x + 2.5) * x - 4) * x + 2; + } + + return bc; +} + +template <int CC, class T> +static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + // get source image size + int width = p_src_width; + int height = p_src_height; + double xfac = (double)width / p_dst_width; + double yfac = (double)height / p_dst_height; + // coordinates of source points and coefficients + double ox, oy, dx, dy, k1, k2; + int ox1, oy1, ox2, oy2; + // destination pixel values + // width and height decreased by 1 + int ymax = height - 1; + int xmax = width - 1; + // temporary pointer + + for (uint32_t y = 0; y < p_dst_height; y++) { + // Y coordinates + oy = (double)y * yfac - 0.5f; + oy1 = (int)oy; + dy = oy - (double)oy1; + + for (uint32_t x = 0; x < p_dst_width; x++) { + // X coordinates + ox = (double)x * xfac - 0.5f; + ox1 = (int)ox; + dx = ox - (double)ox1; + + // initial pixel value + + T *__restrict dst = ((T *)p_dst) + (y * p_dst_width + x) * CC; + + double color[CC]; + for (int i = 0; i < CC; i++) { + color[i] = 0; + } + + for (int n = -1; n < 3; n++) { + // get Y coefficient + k1 = _bicubic_interp_kernel(dy - (double)n); + + oy2 = oy1 + n; + if (oy2 < 0) { + oy2 = 0; + } + if (oy2 > ymax) { + oy2 = ymax; + } + + for (int m = -1; m < 3; m++) { + // get X coefficient + k2 = k1 * _bicubic_interp_kernel((double)m - dx); + + ox2 = ox1 + m; + if (ox2 < 0) { + ox2 = 0; + } + if (ox2 > xmax) { + ox2 = xmax; + } + + // get pixel of original image + const T *__restrict p = ((T *)p_src) + (oy2 * p_src_width + ox2) * CC; + + for (int i = 0; i < CC; i++) { + if (sizeof(T) == 2) { //half float + color[i] = Math::half_to_float(p[i]); + } else { + color[i] += p[i] * k2; + } + } + } + } + + for (int i = 0; i < CC; i++) { + if (sizeof(T) == 1) { //byte + dst[i] = CLAMP(Math::fast_ftoi(color[i]), 0, 255); + } else if (sizeof(T) == 2) { //half float + dst[i] = Math::make_half_float(color[i]); + } else { + dst[i] = color[i]; + } + } + } + } +} + +template <int CC, class T> +static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + enum { + FRAC_BITS = 8, + FRAC_LEN = (1 << FRAC_BITS), + FRAC_HALF = (FRAC_LEN >> 1), + FRAC_MASK = FRAC_LEN - 1 + }; + + for (uint32_t i = 0; i < p_dst_height; i++) { + // Add 0.5 in order to interpolate based on pixel center + uint32_t src_yofs_up_fp = (i + 0.5) * p_src_height * FRAC_LEN / p_dst_height; + // Calculate nearest src pixel center above current, and truncate to get y index + uint32_t src_yofs_up = src_yofs_up_fp >= FRAC_HALF ? (src_yofs_up_fp - FRAC_HALF) >> FRAC_BITS : 0; + uint32_t src_yofs_down = (src_yofs_up_fp + FRAC_HALF) >> FRAC_BITS; + if (src_yofs_down >= p_src_height) { + src_yofs_down = p_src_height - 1; + } + // Calculate distance to pixel center of src_yofs_up + uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK; + src_yofs_frac = src_yofs_frac >= FRAC_HALF ? src_yofs_frac - FRAC_HALF : src_yofs_frac + FRAC_HALF; + + uint32_t y_ofs_up = src_yofs_up * p_src_width * CC; + uint32_t y_ofs_down = src_yofs_down * p_src_width * CC; + + for (uint32_t j = 0; j < p_dst_width; j++) { + uint32_t src_xofs_left_fp = (j + 0.5) * p_src_width * FRAC_LEN / p_dst_width; + uint32_t src_xofs_left = src_xofs_left_fp >= FRAC_HALF ? (src_xofs_left_fp - FRAC_HALF) >> FRAC_BITS : 0; + uint32_t src_xofs_right = (src_xofs_left_fp + FRAC_HALF) >> FRAC_BITS; + if (src_xofs_right >= p_src_width) { + src_xofs_right = p_src_width - 1; + } + uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK; + src_xofs_frac = src_xofs_frac >= FRAC_HALF ? src_xofs_frac - FRAC_HALF : src_xofs_frac + FRAC_HALF; + + src_xofs_left *= CC; + src_xofs_right *= CC; + + for (uint32_t l = 0; l < CC; l++) { + if (sizeof(T) == 1) { //uint8 + uint32_t p00 = p_src[y_ofs_up + src_xofs_left + l] << FRAC_BITS; + uint32_t p10 = p_src[y_ofs_up + src_xofs_right + l] << FRAC_BITS; + uint32_t p01 = p_src[y_ofs_down + src_xofs_left + l] << FRAC_BITS; + uint32_t p11 = p_src[y_ofs_down + src_xofs_right + l] << FRAC_BITS; + + uint32_t interp_up = p00 + (((p10 - p00) * src_xofs_frac) >> FRAC_BITS); + 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; + } else if (sizeof(T) == 2) { //half float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = Math::half_to_float(src[y_ofs_up + src_xofs_left + l]); + float p10 = Math::half_to_float(src[y_ofs_up + src_xofs_right + l]); + float p01 = Math::half_to_float(src[y_ofs_down + src_xofs_left + l]); + float p11 = Math::half_to_float(src[y_ofs_down + src_xofs_right + l]); + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = Math::make_half_float(interp); + } else if (sizeof(T) == 4) { //float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = src[y_ofs_up + src_xofs_left + l]; + float p10 = src[y_ofs_up + src_xofs_right + l]; + float p01 = src[y_ofs_down + src_xofs_left + l]; + float p11 = src[y_ofs_down + src_xofs_right + l]; + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = interp; + } + } + } + } +} + +template <int CC, class T> +static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + for (uint32_t i = 0; i < p_dst_height; i++) { + uint32_t src_yofs = i * p_src_height / p_dst_height; + uint32_t y_ofs = src_yofs * p_src_width * CC; + + for (uint32_t j = 0; j < p_dst_width; j++) { + uint32_t src_xofs = j * p_src_width / p_dst_width; + src_xofs *= CC; + + for (uint32_t l = 0; l < CC; l++) { + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + T p = src[y_ofs + src_xofs + l]; + dst[i * p_dst_width * CC + j * CC + l] = p; + } + } + } +} + +#define LANCZOS_TYPE 3 + +static float _lanczos(float p_x) { + return Math::abs(p_x) >= LANCZOS_TYPE ? 0 : Math::sincn(p_x) * Math::sincn(p_x / LANCZOS_TYPE); +} + +template <int CC, class T> +static void _scale_lanczos(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + int32_t src_width = p_src_width; + int32_t src_height = p_src_height; + int32_t dst_height = p_dst_height; + int32_t dst_width = p_dst_width; + + uint32_t buffer_size = src_height * dst_width * CC; + float *buffer = memnew_arr(float, buffer_size); // Store the first pass in a buffer + + { // FIRST PASS (horizontal) + + float x_scale = float(src_width) / float(dst_width); + + float scale_factor = MAX(x_scale, 1); // A larger kernel is required only when downscaling + int32_t half_kernel = LANCZOS_TYPE * scale_factor; + + float *kernel = memnew_arr(float, half_kernel * 2); + + for (int32_t buffer_x = 0; buffer_x < dst_width; buffer_x++) { + // The corresponding point on the source image + float src_x = (buffer_x + 0.5f) * x_scale; // Offset by 0.5 so it uses the pixel's center + int32_t start_x = MAX(0, int32_t(src_x) - half_kernel + 1); + int32_t end_x = MIN(src_width - 1, int32_t(src_x) + half_kernel); + + // Create the kernel used by all the pixels of the column + for (int32_t target_x = start_x; target_x <= end_x; target_x++) { + kernel[target_x - start_x] = _lanczos((target_x + 0.5f - src_x) / scale_factor); + } + + for (int32_t buffer_y = 0; buffer_y < src_height; buffer_y++) { + float pixel[CC] = { 0 }; + float weight = 0; + + for (int32_t target_x = start_x; target_x <= end_x; target_x++) { + float lanczos_val = kernel[target_x - start_x]; + weight += lanczos_val; + + const T *__restrict src_data = ((const T *)p_src) + (buffer_y * src_width + target_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + if (sizeof(T) == 2) { //half float + pixel[i] += Math::half_to_float(src_data[i]) * lanczos_val; + } else { + pixel[i] += src_data[i] * lanczos_val; + } + } + } + + float *dst_data = ((float *)buffer) + (buffer_y * dst_width + buffer_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + dst_data[i] = pixel[i] / weight; // Normalize the sum of all the samples + } + } + } + + memdelete_arr(kernel); + } // End of first pass + + { // SECOND PASS (vertical + result) + + float y_scale = float(src_height) / float(dst_height); + + float scale_factor = MAX(y_scale, 1); + int32_t half_kernel = LANCZOS_TYPE * scale_factor; + + float *kernel = memnew_arr(float, half_kernel * 2); + + for (int32_t dst_y = 0; dst_y < dst_height; dst_y++) { + float buffer_y = (dst_y + 0.5f) * y_scale; + int32_t start_y = MAX(0, int32_t(buffer_y) - half_kernel + 1); + int32_t end_y = MIN(src_height - 1, int32_t(buffer_y) + half_kernel); + + for (int32_t target_y = start_y; target_y <= end_y; target_y++) { + kernel[target_y - start_y] = _lanczos((target_y + 0.5f - buffer_y) / scale_factor); + } + + for (int32_t dst_x = 0; dst_x < dst_width; dst_x++) { + float pixel[CC] = { 0 }; + float weight = 0; + + for (int32_t target_y = start_y; target_y <= end_y; target_y++) { + float lanczos_val = kernel[target_y - start_y]; + weight += lanczos_val; + + float *buffer_data = ((float *)buffer) + (target_y * dst_width + dst_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + pixel[i] += buffer_data[i] * lanczos_val; + } + } + + T *dst_data = ((T *)p_dst) + (dst_y * dst_width + dst_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + pixel[i] /= weight; + + if (sizeof(T) == 1) { //byte + dst_data[i] = CLAMP(Math::fast_ftoi(pixel[i]), 0, 255); + } else if (sizeof(T) == 2) { //half float + dst_data[i] = Math::make_half_float(pixel[i]); + } else { // float + dst_data[i] = pixel[i]; + } + } + } + } + + memdelete_arr(kernel); + } // End of second pass + + memdelete_arr(buffer); +} + +static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, float p_alpha, uint32_t p_width, uint32_t p_height, uint32_t p_pixel_size) { + uint16_t alpha = MIN((uint16_t)(p_alpha * 256.0f), 256); + + for (uint32_t i = 0; i < p_width * p_height * p_pixel_size; i++) { + p_dst[i] = (p_dst[i] * (256 - alpha) + p_src[i] * alpha) >> 8; + } +} + +bool Image::is_size_po2() const { + return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height); +} + +void Image::resize_to_po2(bool p_square, Interpolation p_interpolation) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); + + int w = next_power_of_2(width); + int h = next_power_of_2(height); + if (p_square) { + w = h = MAX(w, h); + } + + if (w == width && h == height) { + if (!p_square || w == h) { + return; //nothing to do + } + } + + resize(w, h, p_interpolation); +} + +void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { + ERR_FAIL_COND_MSG(data.size() == 0, "Cannot resize image before creating it, use create() or create_from_data() first."); + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); + + bool mipmap_aware = p_interpolation == INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */; + + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); + ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS)); + + if (p_width == width && p_height == height) { + return; + } + + Image dst(p_width, p_height, false, format); + + // Setup mipmap-aware scaling + Image dst2; + int mip1 = 0; + int mip2 = 0; + float mip1_weight = 0; + if (mipmap_aware) { + float avg_scale = ((float)p_width / width + (float)p_height / height) * 0.5f; + if (avg_scale >= 1.0f) { + mipmap_aware = false; + } else { + float level = Math::log(1.0f / avg_scale) / Math::log(2.0f); + mip1 = CLAMP((int)Math::floor(level), 0, get_mipmap_count()); + mip2 = CLAMP((int)Math::ceil(level), 0, get_mipmap_count()); + mip1_weight = 1.0f - (level - mip1); + } + } + bool interpolate_mipmaps = mipmap_aware && mip1 != mip2; + if (interpolate_mipmaps) { + dst2.create(p_width, p_height, false, format); + } + + bool had_mipmaps = mipmaps; + if (interpolate_mipmaps && !had_mipmaps) { + generate_mipmaps(); + } + // -- + + const uint8_t *r = data.ptr(); + const unsigned char *r_ptr = r; + + uint8_t *w = dst.data.ptrw(); + unsigned char *w_ptr = w; + + switch (p_interpolation) { + case INTERPOLATE_NEAREST: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_nearest<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_nearest<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_nearest<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_nearest<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_nearest<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_nearest<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_nearest<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_nearest<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_nearest<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_nearest<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_nearest<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_nearest<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + + } break; + case INTERPOLATE_BILINEAR: + case INTERPOLATE_TRILINEAR: { + for (int i = 0; i < 2; ++i) { + int src_width; + int src_height; + const unsigned char *src_ptr; + + if (!mipmap_aware) { + if (i == 0) { + // Standard behavior + src_width = width; + src_height = height; + src_ptr = r_ptr; + } else { + // No need for a second iteration + break; + } + } else { + if (i == 0) { + // Read from the first mipmap that will be interpolated + // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) + int offs; + _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); + src_ptr = r_ptr + offs; + } else if (!interpolate_mipmaps) { + // No need generate a second image + break; + } else { + // Switch to read from the second mipmap that will be interpolated + int offs; + _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); + src_ptr = r_ptr + offs; + // Switch to write to the second destination image + w = dst2.data.ptrw(); + w_ptr = w; + } + } + + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_bilinear<1, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 2: + _scale_bilinear<2, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 3: + _scale_bilinear<3, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 4: + _scale_bilinear<4, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_bilinear<1, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 8: + _scale_bilinear<2, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 12: + _scale_bilinear<3, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 16: + _scale_bilinear<4, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_bilinear<1, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 4: + _scale_bilinear<2, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 6: + _scale_bilinear<3, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 8: + _scale_bilinear<4, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } + } + + if (interpolate_mipmaps) { + // Switch to read again from the first scaled mipmap to overlay it over the second + r = dst.data.ptr(); + _overlay(r, w, mip1_weight, p_width, p_height, get_format_pixel_size(format)); + } + + } break; + case INTERPOLATE_CUBIC: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_cubic<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_cubic<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_cubic<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_cubic<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_cubic<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_cubic<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_cubic<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_cubic<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_cubic<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_cubic<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_cubic<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_cubic<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + } break; + case INTERPOLATE_LANCZOS: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_lanczos<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_lanczos<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_lanczos<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_lanczos<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_lanczos<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_lanczos<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_lanczos<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_lanczos<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_lanczos<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_lanczos<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_lanczos<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_lanczos<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + } break; + } + + if (interpolate_mipmaps) { + dst._copy_internals_from(dst2); + } + + if (had_mipmaps) { + dst.generate_mipmaps(); + } + + _copy_internals_from(dst); +} + +void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot crop in compressed or custom image formats."); + + ERR_FAIL_COND_MSG(p_x < 0, "Start x position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + itos(MAX_HEIGHT) + "."); + + /* to save memory, cropping should be done in-place, however, since this function + will most likely either not be used much, or in critical areas, for now it won't, because + it's a waste of time. */ + + if (p_width == width && p_height == height && p_x == 0 && p_y == 0) { + return; + } + + uint8_t pdata[16]; //largest is 16 + uint32_t pixel_size = get_format_pixel_size(format); + + Image dst(p_width, p_height, false, format); + + { + const uint8_t *r = data.ptr(); + uint8_t *w = dst.data.ptrw(); + + int m_h = p_y + p_height; + int m_w = p_x + p_width; + for (int y = p_y; y < m_h; y++) { + for (int x = p_x; x < m_w; x++) { + if ((x >= width || y >= height)) { + for (uint32_t i = 0; i < pixel_size; i++) { + pdata[i] = 0; + } + } else { + _get_pixelb(x, y, pixel_size, r, pdata); + } + + dst._put_pixelb(x - p_x, y - p_y, pixel_size, w, pdata); + } + } + } + + if (has_mipmaps()) { + dst.generate_mipmaps(); + } + _copy_internals_from(dst); +} + +void Image::crop(int p_width, int p_height) { + crop_from_point(0, 0, p_width, p_height); +} + +void Image::flip_y() { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_y in compressed or custom image formats."); + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + { + uint8_t *w = data.ptrw(); + uint8_t up[16]; + uint8_t down[16]; + uint32_t pixel_size = get_format_pixel_size(format); + + for (int y = 0; y < height / 2; y++) { + for (int x = 0; x < width; x++) { + _get_pixelb(x, y, pixel_size, w, up); + _get_pixelb(x, height - y - 1, pixel_size, w, down); + + _put_pixelb(x, height - y - 1, pixel_size, w, up); + _put_pixelb(x, y, pixel_size, w, down); + } + } + } + + if (used_mipmaps) { + generate_mipmaps(); + } +} + +void Image::flip_x() { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_x in compressed or custom image formats."); + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + { + uint8_t *w = data.ptrw(); + uint8_t up[16]; + uint8_t down[16]; + uint32_t pixel_size = get_format_pixel_size(format); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width / 2; x++) { + _get_pixelb(x, y, pixel_size, w, up); + _get_pixelb(width - x - 1, y, pixel_size, w, down); + + _put_pixelb(width - x - 1, y, pixel_size, w, up); + _put_pixelb(x, y, pixel_size, w, down); + } + } + } + + if (used_mipmaps) { + generate_mipmaps(); + } +} + +/// Get mipmap size and offset. +int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { + // Data offset in mipmaps (including the original texture). + int size = 0; + + int w = p_width; + int h = p_height; + + // Current mipmap index in the loop below. p_mipmaps is the target mipmap index. + // In this function, mipmap 0 represents the first mipmap instead of the original texture. + int mm = 0; + + int pixsize = get_format_pixel_size(p_format); + int pixshift = get_format_pixel_rshift(p_format); + int block = get_format_block_size(p_format); + + // Technically, you can still compress up to 1 px no matter the format, so commenting this. + //int minw, minh; + //get_format_min_pixel_size(p_format, minw, minh); + int minw = 1, minh = 1; + + while (true) { + int bw = w % block != 0 ? w + (block - w % block) : w; + int bh = h % block != 0 ? h + (block - h % block) : h; + + int s = bw * bh; + + s *= pixsize; + s >>= pixshift; + + size += s; + + if (p_mipmaps >= 0) { + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } else { + if (w == minw && h == minh) { + break; + } + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } + + // Set mipmap size. + // It might be necessary to put this after the minimum mipmap size check because of the possible occurrence of "1 >> 1". + if (r_mm_width) { + *r_mm_width = bw >> 1; + } + if (r_mm_height) { + *r_mm_height = bh >> 1; + } + + // Reach target mipmap. + if (p_mipmaps >= 0 && mm == p_mipmaps) { + break; + } + + mm++; + } + + r_mipmaps = mm; + return size; +} + +bool Image::_can_modify(Format p_format) const { + return p_format <= FORMAT_RGBE9995; +} + +template <class Component, int CC, bool renormalize, + void (*average_func)(Component &, const Component &, const Component &, const Component &, const Component &), + void (*renormalize_func)(Component *)> +static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint32_t p_width, uint32_t p_height) { + //fast power of 2 mipmap generation + uint32_t dst_w = MAX(p_width >> 1, 1); + uint32_t dst_h = MAX(p_height >> 1, 1); + + int right_step = (p_width == 1) ? 0 : CC; + int down_step = (p_height == 1) ? 0 : (p_width * CC); + + for (uint32_t i = 0; i < dst_h; i++) { + const Component *rup_ptr = &p_src[i * 2 * down_step]; + const Component *rdown_ptr = rup_ptr + down_step; + Component *dst_ptr = &p_dst[i * dst_w * CC]; + uint32_t count = dst_w; + + while (count) { + count--; + for (int j = 0; j < CC; j++) { + average_func(dst_ptr[j], rup_ptr[j], rup_ptr[j + right_step], rdown_ptr[j], rdown_ptr[j + right_step]); + } + + if (renormalize) { + renormalize_func(dst_ptr); + } + + dst_ptr += CC; + rup_ptr += right_step * 2; + rdown_ptr += right_step * 2; + } + } +} + +void Image::shrink_x2() { + ERR_FAIL_COND(data.size() == 0); + + if (mipmaps) { + //just use the lower mipmap as base and copy all + Vector<uint8_t> new_img; + + int ofs = get_mipmap_offset(1); + + int new_size = data.size() - ofs; + new_img.resize(new_size); + ERR_FAIL_COND(new_img.size() == 0); + + { + uint8_t *w = new_img.ptrw(); + const uint8_t *r = data.ptr(); + + memcpy(w, &r[ofs], new_size); + } + + width = MAX(width / 2, 1); + height = MAX(height / 2, 1); + data = new_img; + + } else { + Vector<uint8_t> new_img; + + ERR_FAIL_COND(!_can_modify(format)); + int ps = get_format_pixel_size(format); + new_img.resize((width / 2) * (height / 2) * ps); + ERR_FAIL_COND(new_img.size() == 0); + ERR_FAIL_COND(data.size() == 0); + + { + uint8_t *w = new_img.ptrw(); + const uint8_t *r = data.ptr(); + + switch (format) { + case FORMAT_L8: + case FORMAT_R8: + _generate_po2_mipmap<uint8_t, 1, false, Image::average_4_uint8, Image::renormalize_uint8>(r, w, width, height); + break; + case FORMAT_LA8: + _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(r, w, width, height); + break; + case FORMAT_RG8: + _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(r, w, width, height); + break; + case FORMAT_RGB8: + _generate_po2_mipmap<uint8_t, 3, false, Image::average_4_uint8, Image::renormalize_uint8>(r, w, width, height); + break; + case FORMAT_RGBA8: + _generate_po2_mipmap<uint8_t, 4, false, Image::average_4_uint8, Image::renormalize_uint8>(r, w, width, height); + break; + + case FORMAT_RF: + _generate_po2_mipmap<float, 1, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r), reinterpret_cast<float *>(w), width, height); + break; + case FORMAT_RGF: + _generate_po2_mipmap<float, 2, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r), reinterpret_cast<float *>(w), width, height); + break; + case FORMAT_RGBF: + _generate_po2_mipmap<float, 3, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r), reinterpret_cast<float *>(w), width, height); + break; + case FORMAT_RGBAF: + _generate_po2_mipmap<float, 4, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(r), reinterpret_cast<float *>(w), width, height); + break; + + case FORMAT_RH: + _generate_po2_mipmap<uint16_t, 1, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r), reinterpret_cast<uint16_t *>(w), width, height); + break; + case FORMAT_RGH: + _generate_po2_mipmap<uint16_t, 2, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r), reinterpret_cast<uint16_t *>(w), width, height); + break; + case FORMAT_RGBH: + _generate_po2_mipmap<uint16_t, 3, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r), reinterpret_cast<uint16_t *>(w), width, height); + break; + case FORMAT_RGBAH: + _generate_po2_mipmap<uint16_t, 4, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(r), reinterpret_cast<uint16_t *>(w), width, height); + break; + + case FORMAT_RGBE9995: + _generate_po2_mipmap<uint32_t, 1, false, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(r), reinterpret_cast<uint32_t *>(w), width, height); + break; + default: { + } + } + } + + width /= 2; + height /= 2; + data = new_img; + } +} + +void Image::normalize() { + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Color c = get_pixel(x, y); + Vector3 v(c.r * 2.0 - 1.0, c.g * 2.0 - 1.0, c.b * 2.0 - 1.0); + v.normalize(); + c.r = v.x * 0.5 + 0.5; + c.g = v.y * 0.5 + 0.5; + c.b = v.z * 0.5 + 0.5; + set_pixel(x, y, c); + } + } + + if (used_mipmaps) { + generate_mipmaps(true); + } +} + +Error Image::generate_mipmaps(bool p_renormalize) { + ERR_FAIL_COND_V_MSG(!_can_modify(format), ERR_UNAVAILABLE, "Cannot generate mipmaps in compressed or custom image formats."); + + ERR_FAIL_COND_V_MSG(format == FORMAT_RGBA4444, ERR_UNAVAILABLE, "Cannot generate mipmaps from RGBA4444 format."); + + ERR_FAIL_COND_V_MSG(width == 0 || height == 0, ERR_UNCONFIGURED, "Cannot generate mipmaps with width or height equal to 0."); + + int mmcount; + + int size = _get_dst_image_size(width, height, format, mmcount); + + data.resize(size); + + uint8_t *wp = data.ptrw(); + + int prev_ofs = 0; + int prev_h = height; + int prev_w = width; + + for (int i = 1; i <= mmcount; i++) { + int ofs, w, h; + _get_mipmap_offset_and_size(i, ofs, w, h); + + switch (format) { + case FORMAT_L8: + case FORMAT_R8: + _generate_po2_mipmap<uint8_t, 1, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + case FORMAT_LA8: + case FORMAT_RG8: + _generate_po2_mipmap<uint8_t, 2, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + case FORMAT_RGB8: + if (p_renormalize) { + _generate_po2_mipmap<uint8_t, 3, true, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } else { + _generate_po2_mipmap<uint8_t, 3, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } + + break; + case FORMAT_RGBA8: + if (p_renormalize) { + _generate_po2_mipmap<uint8_t, 4, true, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } else { + _generate_po2_mipmap<uint8_t, 4, false, Image::average_4_uint8, Image::renormalize_uint8>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } + break; + case FORMAT_RF: + _generate_po2_mipmap<float, 1, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGF: + _generate_po2_mipmap<float, 2, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBF: + if (p_renormalize) { + _generate_po2_mipmap<float, 3, true, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap<float, 3, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBAF: + if (p_renormalize) { + _generate_po2_mipmap<float, 4, true, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap<float, 4, false, Image::average_4_float, Image::renormalize_float>(reinterpret_cast<const float *>(&wp[prev_ofs]), reinterpret_cast<float *>(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RH: + _generate_po2_mipmap<uint16_t, 1, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGH: + _generate_po2_mipmap<uint16_t, 2, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBH: + if (p_renormalize) { + _generate_po2_mipmap<uint16_t, 3, true, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap<uint16_t, 3, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBAH: + if (p_renormalize) { + _generate_po2_mipmap<uint16_t, 4, true, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap<uint16_t, 4, false, Image::average_4_half, Image::renormalize_half>(reinterpret_cast<const uint16_t *>(&wp[prev_ofs]), reinterpret_cast<uint16_t *>(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBE9995: + if (p_renormalize) { + _generate_po2_mipmap<uint32_t, 1, true, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(&wp[prev_ofs]), reinterpret_cast<uint32_t *>(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap<uint32_t, 1, false, Image::average_4_rgbe9995, Image::renormalize_rgbe9995>(reinterpret_cast<const uint32_t *>(&wp[prev_ofs]), reinterpret_cast<uint32_t *>(&wp[ofs]), prev_w, prev_h); + } + + break; + default: { + } + } + + prev_ofs = ofs; + prev_w = w; + prev_h = h; + } + + mipmaps = true; + + return OK; +} + +Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, const Ref<Image> &p_normal_map) { + Vector<double> normal_sat_vec; //summed area table + double *normal_sat = nullptr; //summed area table for normal map + int normal_w = 0, normal_h = 0; + + ERR_FAIL_COND_V_MSG(p_normal_map.is_null() || p_normal_map->is_empty(), ERR_INVALID_PARAMETER, "Must provide a valid normal map for roughness mipmaps"); + + Ref<Image> nm = p_normal_map->duplicate(); + if (nm->is_compressed()) { + nm->decompress(); + } + + normal_w = nm->get_width(); + normal_h = nm->get_height(); + + normal_sat_vec.resize(normal_w * normal_h * 3); + + normal_sat = normal_sat_vec.ptrw(); + + //create summed area table + + for (int y = 0; y < normal_h; y++) { + double line_sum[3] = { 0, 0, 0 }; + for (int x = 0; x < normal_w; x++) { + double normal[3]; + Color color = nm->get_pixel(x, y); + normal[0] = color.r * 2.0 - 1.0; + normal[1] = color.g * 2.0 - 1.0; + normal[2] = Math::sqrt(MAX(0.0, 1.0 - (normal[0] * normal[0] + normal[1] * normal[1]))); //reconstruct if missing + + line_sum[0] += normal[0]; + line_sum[1] += normal[1]; + line_sum[2] += normal[2]; + + uint32_t ofs = (y * normal_w + x) * 3; + + normal_sat[ofs + 0] = line_sum[0]; + normal_sat[ofs + 1] = line_sum[1]; + normal_sat[ofs + 2] = line_sum[2]; + + if (y > 0) { + uint32_t prev_ofs = ((y - 1) * normal_w + x) * 3; + normal_sat[ofs + 0] += normal_sat[prev_ofs + 0]; + normal_sat[ofs + 1] += normal_sat[prev_ofs + 1]; + normal_sat[ofs + 2] += normal_sat[prev_ofs + 2]; + } + } + } + +#if 0 + { + Vector3 beg(normal_sat_vec[0], normal_sat_vec[1], normal_sat_vec[2]); + Vector3 end(normal_sat_vec[normal_sat_vec.size() - 3], normal_sat_vec[normal_sat_vec.size() - 2], normal_sat_vec[normal_sat_vec.size() - 1]); + Vector3 avg = (end - beg) / (normal_w * normal_h); + print_line("average: " + avg); + } +#endif + + int mmcount; + + _get_dst_image_size(width, height, format, mmcount); + + uint8_t *base_ptr = data.ptrw(); + + for (int i = 1; i <= mmcount; i++) { + int ofs, w, h; + _get_mipmap_offset_and_size(i, ofs, w, h); + uint8_t *ptr = &base_ptr[ofs]; + + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + int from_x = x * normal_w / w; + int from_y = y * normal_h / h; + int to_x = (x + 1) * normal_w / w; + int to_y = (y + 1) * normal_h / h; + to_x = MIN(to_x - 1, normal_w); + to_y = MIN(to_y - 1, normal_h); + + int size_x = (to_x - from_x) + 1; + int size_y = (to_y - from_y) + 1; + + //summed area table version (much faster) + + double avg[3] = { 0, 0, 0 }; + + if (from_x > 0 && from_y > 0) { + uint32_t tofs = ((from_y - 1) * normal_w + (from_x - 1)) * 3; + avg[0] += normal_sat[tofs + 0]; + avg[1] += normal_sat[tofs + 1]; + avg[2] += normal_sat[tofs + 2]; + } + + if (from_y > 0) { + uint32_t tofs = ((from_y - 1) * normal_w + to_x) * 3; + avg[0] -= normal_sat[tofs + 0]; + avg[1] -= normal_sat[tofs + 1]; + avg[2] -= normal_sat[tofs + 2]; + } + + if (from_x > 0) { + uint32_t tofs = (to_y * normal_w + (from_x - 1)) * 3; + avg[0] -= normal_sat[tofs + 0]; + avg[1] -= normal_sat[tofs + 1]; + avg[2] -= normal_sat[tofs + 2]; + } + + uint32_t tofs = (to_y * normal_w + to_x) * 3; + avg[0] += normal_sat[tofs + 0]; + avg[1] += normal_sat[tofs + 1]; + avg[2] += normal_sat[tofs + 2]; + + double div = double(size_x * size_y); + Vector3 vec(avg[0] / div, avg[1] / div, avg[2] / div); + + float r = vec.length(); + + int pixel_ofs = y * w + x; + Color c = _get_color_at_ofs(ptr, pixel_ofs); + + float roughness = 0; + + switch (p_roughness_channel) { + case ROUGHNESS_CHANNEL_R: { + roughness = c.r; + } break; + case ROUGHNESS_CHANNEL_G: { + roughness = c.g; + } break; + case ROUGHNESS_CHANNEL_B: { + roughness = c.b; + } break; + case ROUGHNESS_CHANNEL_L: { + roughness = c.get_v(); + } break; + case ROUGHNESS_CHANNEL_A: { + roughness = c.a; + } break; + } + + float variance = 0; + if (r < 1.0f) { + float r2 = r * r; + float kappa = (3.0f * r - r * r2) / (1.0f - r2); + variance = 0.25f / kappa; + } + + float threshold = 0.4; + roughness = Math::sqrt(roughness * roughness + MIN(3.0f * variance, threshold * threshold)); + + switch (p_roughness_channel) { + case ROUGHNESS_CHANNEL_R: { + c.r = roughness; + } break; + case ROUGHNESS_CHANNEL_G: { + c.g = roughness; + } break; + case ROUGHNESS_CHANNEL_B: { + c.b = roughness; + } break; + case ROUGHNESS_CHANNEL_L: { + c.r = roughness; + c.g = roughness; + c.b = roughness; + } break; + case ROUGHNESS_CHANNEL_A: { + c.a = roughness; + } break; + } + + _set_color_at_ofs(ptr, pixel_ofs, c); + } + } +#if 0 + { + int size = get_mipmap_byte_size(i); + print_line("size for mimpap " + itos(i) + ": " + itos(size)); + Vector<uint8_t> imgdata; + imgdata.resize(size); + + + uint8_t* wr = imgdata.ptrw(); + memcpy(wr.ptr(), ptr, size); + wr = uint8_t*(); + Ref<Image> im; + im.instantiate(); + im->create(w, h, false, format, imgdata); + im->save_png("res://mipmap_" + itos(i) + ".png"); + } +#endif + } + + return OK; +} + +void Image::clear_mipmaps() { + if (!mipmaps) { + return; + } + + if (is_empty()) { + return; + } + + int ofs, w, h; + _get_mipmap_offset_and_size(1, ofs, w, h); + data.resize(ofs); + + mipmaps = false; +} + +bool Image::is_empty() const { + return (data.size() == 0); +} + +Vector<uint8_t> Image::get_data() const { + return data; +} + +void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); + ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS)); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); + + int mm = 0; + int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + data.resize(size); + + { + uint8_t *w = data.ptrw(); + memset(w, 0, size); + } + + width = p_width; + height = p_height; + mipmaps = p_use_mipmaps; + format = p_format; +} + +void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) { + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); + ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS)); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); + + int mm; + int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + + ERR_FAIL_COND_MSG(p_data.size() != size, "Expected data size of " + itos(size) + " bytes in Image::create(), got instead " + itos(p_data.size()) + " bytes."); + + height = p_height; + width = p_width; + format = p_format; + data = p_data; + + mipmaps = p_use_mipmaps; +} + +void Image::create(const char **p_xpm) { + int size_width = 0; + int size_height = 0; + int pixelchars = 0; + mipmaps = false; + bool has_alpha = false; + + enum Status { + READING_HEADER, + READING_COLORS, + READING_PIXELS, + DONE + }; + + Status status = READING_HEADER; + int line = 0; + + HashMap<String, Color> colormap; + int colormap_size = 0; + uint32_t pixel_size = 0; + uint8_t *data_write = nullptr; + + while (status != DONE) { + const char *line_ptr = p_xpm[line]; + + switch (status) { + case READING_HEADER: { + String line_str = line_ptr; + line_str.replace("\t", " "); + + size_width = line_str.get_slicec(' ', 0).to_int(); + size_height = line_str.get_slicec(' ', 1).to_int(); + colormap_size = line_str.get_slicec(' ', 2).to_int(); + pixelchars = line_str.get_slicec(' ', 3).to_int(); + ERR_FAIL_COND(colormap_size > 32766); + ERR_FAIL_COND(pixelchars > 5); + ERR_FAIL_COND(size_width > 32767); + ERR_FAIL_COND(size_height > 32767); + status = READING_COLORS; + } break; + case READING_COLORS: { + String colorstring; + for (int i = 0; i < pixelchars; i++) { + colorstring += *line_ptr; + line_ptr++; + } + //skip spaces + while (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == 0) { + if (*line_ptr == 0) { + break; + } + line_ptr++; + } + if (*line_ptr == 'c') { + line_ptr++; + while (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == 0) { + if (*line_ptr == 0) { + break; + } + line_ptr++; + } + + if (*line_ptr == '#') { + line_ptr++; + uint8_t col_r = 0; + uint8_t col_g = 0; + uint8_t col_b = 0; + //uint8_t col_a=255; + + for (int i = 0; i < 6; i++) { + char v = line_ptr[i]; + + if (v >= '0' && v <= '9') { + v -= '0'; + } else if (v >= 'A' && v <= 'F') { + v = (v - 'A') + 10; + } else if (v >= 'a' && v <= 'f') { + v = (v - 'a') + 10; + } else { + break; + } + + switch (i) { + case 0: + col_r = v << 4; + break; + case 1: + col_r |= v; + break; + case 2: + col_g = v << 4; + break; + case 3: + col_g |= v; + break; + case 4: + col_b = v << 4; + break; + case 5: + col_b |= v; + break; + } + } + + // magenta mask + if (col_r == 255 && col_g == 0 && col_b == 255) { + colormap[colorstring] = Color(0, 0, 0, 0); + has_alpha = true; + } else { + colormap[colorstring] = Color(col_r / 255.0, col_g / 255.0, col_b / 255.0, 1.0); + } + } + } + if (line == colormap_size) { + status = READING_PIXELS; + create(size_width, size_height, false, has_alpha ? FORMAT_RGBA8 : FORMAT_RGB8); + data_write = data.ptrw(); + pixel_size = has_alpha ? 4 : 3; + } + } break; + case READING_PIXELS: { + int y = line - colormap_size - 1; + for (int x = 0; x < size_width; x++) { + char pixelstr[6] = { 0, 0, 0, 0, 0, 0 }; + for (int i = 0; i < pixelchars; i++) { + pixelstr[i] = line_ptr[x * pixelchars + i]; + } + + Color *colorptr = colormap.getptr(pixelstr); + ERR_FAIL_COND(!colorptr); + uint8_t pixel[4]; + for (uint32_t i = 0; i < pixel_size; i++) { + pixel[i] = CLAMP((*colorptr)[i] * 255, 0, 255); + } + _put_pixelb(x, y, pixel_size, data_write, pixel); + } + + if (y == (size_height - 1)) { + status = DONE; + } + } break; + default: { + } + } + + line++; + } +} +#define DETECT_ALPHA_MAX_THRESHOLD 254 +#define DETECT_ALPHA_MIN_THRESHOLD 2 + +#define DETECT_ALPHA(m_value) \ + { \ + uint8_t value = m_value; \ + if (value < DETECT_ALPHA_MIN_THRESHOLD) \ + bit = true; \ + else if (value < DETECT_ALPHA_MAX_THRESHOLD) { \ + detected = true; \ + break; \ + } \ + } + +#define DETECT_NON_ALPHA(m_value) \ + { \ + uint8_t value = m_value; \ + if (value > 0) { \ + detected = true; \ + break; \ + } \ + } + +bool Image::is_invisible() const { + if (format == FORMAT_L8 || + format == FORMAT_RGB8 || format == FORMAT_RG8) { + return false; + } + + int len = data.size(); + + if (len == 0) { + return true; + } + + int w, h; + _get_mipmap_offset_and_size(1, len, w, h); + + const uint8_t *r = data.ptr(); + const unsigned char *data_ptr = r; + + bool detected = false; + + switch (format) { + case FORMAT_LA8: { + for (int i = 0; i < (len >> 1); i++) { + DETECT_NON_ALPHA(data_ptr[(i << 1) + 1]); + } + + } break; + case FORMAT_RGBA8: { + for (int i = 0; i < (len >> 2); i++) { + DETECT_NON_ALPHA(data_ptr[(i << 2) + 3]) + } + + } break; + + case FORMAT_PVRTC1_2A: + case FORMAT_PVRTC1_4A: + case FORMAT_DXT3: + case FORMAT_DXT5: { + detected = true; + } break; + default: { + } + } + + return !detected; +} + +Image::AlphaMode Image::detect_alpha() const { + int len = data.size(); + + if (len == 0) { + return ALPHA_NONE; + } + + int w, h; + _get_mipmap_offset_and_size(1, len, w, h); + + const uint8_t *r = data.ptr(); + const unsigned char *data_ptr = r; + + bool bit = false; + bool detected = false; + + switch (format) { + case FORMAT_LA8: { + for (int i = 0; i < (len >> 1); i++) { + DETECT_ALPHA(data_ptr[(i << 1) + 1]); + } + + } break; + case FORMAT_RGBA8: { + for (int i = 0; i < (len >> 2); i++) { + DETECT_ALPHA(data_ptr[(i << 2) + 3]) + } + + } break; + case FORMAT_PVRTC1_2A: + case FORMAT_PVRTC1_4A: + case FORMAT_DXT3: + case FORMAT_DXT5: { + detected = true; + } break; + default: { + } + } + + if (detected) { + return ALPHA_BLEND; + } else if (bit) { + return ALPHA_BIT; + } else { + return ALPHA_NONE; + } +} + +Error Image::load(const String &p_path) { +#ifdef DEBUG_ENABLED + if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { + WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource."); + } +#endif + return ImageLoader::load_image(p_path, this); +} + +Error Image::save_png(const String &p_path) const { + if (save_png_func == nullptr) { + return ERR_UNAVAILABLE; + } + + return save_png_func(p_path, Ref<Image>((Image *)this)); +} + +Vector<uint8_t> Image::save_png_to_buffer() const { + if (save_png_buffer_func == nullptr) { + return Vector<uint8_t>(); + } + + return save_png_buffer_func(Ref<Image>((Image *)this)); +} + +Error Image::save_exr(const String &p_path, bool p_grayscale) const { + if (save_exr_func == nullptr) { + return ERR_UNAVAILABLE; + } + + return save_exr_func(p_path, Ref<Image>((Image *)this), p_grayscale); +} + +int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0); +} + +int Image::get_image_required_mipmaps(int p_width, int p_height, Format p_format) { + int mm; + _get_dst_image_size(p_width, p_height, p_format, mm, -1); + return mm; +} + +Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap) { + int mm; + Size2i ret; + _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap, &ret.x, &ret.y); + return ret; +} + +int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { + if (p_mipmap <= 0) { + return 0; + } + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); +} + +int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { + if (p_mipmap <= 0) { + r_w = p_width; + r_h = p_height; + return 0; + } + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1, &r_w, &r_h); +} + +bool Image::is_compressed() const { + return format > FORMAT_RGBE9995; +} + +Error Image::decompress() { + if (((format >= FORMAT_DXT1 && format <= FORMAT_RGTC_RG) || (format == FORMAT_DXT5_RA_AS_RG)) && _image_decompress_bc) { + _image_decompress_bc(this); + } else if (format >= FORMAT_BPTC_RGBA && format <= FORMAT_BPTC_RGBFU && _image_decompress_bptc) { + _image_decompress_bptc(this); + } else if (format >= FORMAT_PVRTC1_2 && format <= FORMAT_PVRTC1_4A && _image_decompress_pvrtc) { + _image_decompress_pvrtc(this); + } else if (format == FORMAT_ETC && _image_decompress_etc1) { + _image_decompress_etc1(this); + } else if (format >= FORMAT_ETC2_R11 && format <= FORMAT_ETC2_RA_AS_RG && _image_decompress_etc2) { + _image_decompress_etc2(this); + } else { + return ERR_UNAVAILABLE; + } + return OK; +} + +Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_lossy_quality) { + ERR_FAIL_INDEX_V_MSG(p_mode, COMPRESS_MAX, ERR_INVALID_PARAMETER, "Invalid compress mode."); + ERR_FAIL_INDEX_V_MSG(p_source, COMPRESS_SOURCE_MAX, ERR_INVALID_PARAMETER, "Invalid compress source."); + return compress_from_channels(p_mode, detect_used_channels(p_source), p_lossy_quality); +} + +Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality) { + switch (p_mode) { + case COMPRESS_S3TC: { + ERR_FAIL_COND_V(!_image_compress_bc_func, ERR_UNAVAILABLE); + _image_compress_bc_func(this, p_lossy_quality, p_channels); + } break; + case COMPRESS_PVRTC1_4: { + ERR_FAIL_COND_V(!_image_compress_pvrtc1_4bpp_func, ERR_UNAVAILABLE); + _image_compress_pvrtc1_4bpp_func(this); + } break; + case COMPRESS_ETC: { + ERR_FAIL_COND_V(!_image_compress_etc1_func, ERR_UNAVAILABLE); + _image_compress_etc1_func(this, p_lossy_quality); + } break; + case COMPRESS_ETC2: { + ERR_FAIL_COND_V(!_image_compress_etc2_func, ERR_UNAVAILABLE); + _image_compress_etc2_func(this, p_lossy_quality, p_channels); + } break; + case COMPRESS_BPTC: { + ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE); + _image_compress_bptc_func(this, p_lossy_quality, p_channels); + } break; + case COMPRESS_MAX: { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } break; + } + + return OK; +} + +Image::Image(const char **p_xpm) { + width = 0; + height = 0; + mipmaps = false; + format = FORMAT_L8; + + create(p_xpm); +} + +Image::Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + width = 0; + height = 0; + mipmaps = p_use_mipmaps; + format = FORMAT_L8; + + create(p_width, p_height, p_use_mipmaps, p_format); +} + +Image::Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data) { + width = 0; + height = 0; + mipmaps = p_mipmaps; + format = FORMAT_L8; + + create(p_width, p_height, p_mipmaps, p_format, p_data); +} + +Rect2 Image::get_used_rect() const { + if (format != FORMAT_LA8 && format != FORMAT_RGBA8 && format != FORMAT_RGBAF && format != FORMAT_RGBAH && format != FORMAT_RGBA4444 && format != FORMAT_RGB565) { + return Rect2(Point2(), Size2(width, height)); + } + + int len = data.size(); + + if (len == 0) { + return Rect2(); + } + + int minx = 0xFFFFFF, miny = 0xFFFFFFF; + int maxx = -1, maxy = -1; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + if (!(get_pixel(i, j).a > 0)) { + continue; + } + if (i > maxx) { + maxx = i; + } + if (j > maxy) { + maxy = j; + } + if (i < minx) { + minx = i; + } + if (j < miny) { + miny = j; + } + } + } + + if (maxx == -1) { + return Rect2(); + } else { + return Rect2(minx, miny, maxx - minx + 1, maxy - miny + 1); + } +} + +Ref<Image> Image::get_rect(const Rect2 &p_area) const { + Ref<Image> img = memnew(Image(p_area.size.x, p_area.size.y, mipmaps, format)); + img->blit_rect(Ref<Image>((Image *)this), p_area, Point2(0, 0)); + return img; +} + +void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(format != p_src->format); + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot blit_rect in compressed or custom image formats."); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).intersection(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.position.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.position.y = ABS(p_dest.y); + } + + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + return; + } + + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).intersection(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); + + uint8_t *wp = data.ptrw(); + uint8_t *dst_data_ptr = wp; + + const uint8_t *rp = p_src->data.ptr(); + const uint8_t *src_data_ptr = rp; + + int pixel_size = get_format_pixel_size(format); + + for (int i = 0; i < dest_rect.size.y; i++) { + for (int j = 0; j < dest_rect.size.x; j++) { + int src_x = clipped_src_rect.position.x + j; + int src_y = clipped_src_rect.position.y + i; + + int dst_x = dest_rect.position.x + j; + int dst_y = dest_rect.position.y + i; + + const uint8_t *src = &src_data_ptr[(src_y * p_src->width + src_x) * pixel_size]; + uint8_t *dst = &dst_data_ptr[(dst_y * width + dst_x) * pixel_size]; + + for (int k = 0; k < pixel_size; k++) { + dst[k] = src[k]; + } + } + } +} + +void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + int maskdsize = p_mask->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(maskdsize == 0); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).intersection(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.position.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.position.y = ABS(p_dest.y); + } + + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + return; + } + + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).intersection(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); + + uint8_t *wp = data.ptrw(); + uint8_t *dst_data_ptr = wp; + + const uint8_t *rp = p_src->data.ptr(); + const uint8_t *src_data_ptr = rp; + + int pixel_size = get_format_pixel_size(format); + + Ref<Image> msk = p_mask; + + for (int i = 0; i < dest_rect.size.y; i++) { + for (int j = 0; j < dest_rect.size.x; j++) { + int src_x = clipped_src_rect.position.x + j; + int src_y = clipped_src_rect.position.y + i; + + if (msk->get_pixel(src_x, src_y).a != 0) { + int dst_x = dest_rect.position.x + j; + int dst_y = dest_rect.position.y + i; + + const uint8_t *src = &src_data_ptr[(src_y * p_src->width + src_x) * pixel_size]; + uint8_t *dst = &dst_data_ptr[(dst_y * width + dst_x) * pixel_size]; + + for (int k = 0; k < pixel_size; k++) { + dst[k] = src[k]; + } + } + } + } +} + +void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).intersection(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.position.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.position.y = ABS(p_dest.y); + } + + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + return; + } + + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).intersection(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); + + Ref<Image> img = p_src; + + for (int i = 0; i < dest_rect.size.y; i++) { + for (int j = 0; j < dest_rect.size.x; j++) { + int src_x = clipped_src_rect.position.x + j; + int src_y = clipped_src_rect.position.y + i; + + int dst_x = dest_rect.position.x + j; + int dst_y = dest_rect.position.y + i; + + Color sc = img->get_pixel(src_x, src_y); + if (sc.a != 0) { + Color dc = get_pixel(dst_x, dst_y); + dc = dc.blend(sc); + set_pixel(dst_x, dst_y, dc); + } + } + } +} + +void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + int maskdsize = p_mask->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(maskdsize == 0); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).intersection(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.position.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.position.y = ABS(p_dest.y); + } + + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) { + return; + } + + Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).intersection(Rect2i(p_dest - src_underscan, clipped_src_rect.size)); + + Ref<Image> img = p_src; + Ref<Image> msk = p_mask; + + for (int i = 0; i < dest_rect.size.y; i++) { + for (int j = 0; j < dest_rect.size.x; j++) { + int src_x = clipped_src_rect.position.x + j; + int src_y = clipped_src_rect.position.y + i; + + // If the mask's pixel is transparent then we skip it + //Color c = msk->get_pixel(src_x, src_y); + //if (c.a == 0) continue; + if (msk->get_pixel(src_x, src_y).a != 0) { + int dst_x = dest_rect.position.x + j; + int dst_y = dest_rect.position.y + i; + + Color sc = img->get_pixel(src_x, src_y); + if (sc.a != 0) { + Color dc = get_pixel(dst_x, dst_y); + dc = dc.blend(sc); + set_pixel(dst_x, dst_y, dc); + } + } + } + } +} + +void Image::fill(const Color &c) { + 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; + + int pixel_size = get_format_pixel_size(format); + + // put first pixel with the format-aware API + set_pixel(0, 0, c); + + 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]; + + for (int k = 0; k < pixel_size; k++) { + dst[k] = dst_data_ptr[k]; + } + } + } +} + +ImageMemLoadFunc Image::_png_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr; + +void (*Image::_image_compress_bc_func)(Image *, float, Image::UsedChannels) = nullptr; +void (*Image::_image_compress_bptc_func)(Image *, float, Image::UsedChannels) = nullptr; +void (*Image::_image_compress_pvrtc1_4bpp_func)(Image *) = nullptr; +void (*Image::_image_compress_etc1_func)(Image *, float) = nullptr; +void (*Image::_image_compress_etc2_func)(Image *, float, Image::UsedChannels) = nullptr; +void (*Image::_image_decompress_pvrtc)(Image *) = nullptr; +void (*Image::_image_decompress_bc)(Image *) = nullptr; +void (*Image::_image_decompress_bptc)(Image *) = nullptr; +void (*Image::_image_decompress_etc1)(Image *) = nullptr; +void (*Image::_image_decompress_etc2)(Image *) = nullptr; + +Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr; +Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr; +Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr; +Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr; +Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr; +Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr; +Ref<Image> (*Image::basis_universal_unpacker)(const Vector<uint8_t> &) = nullptr; + +void Image::_set_data(const Dictionary &p_data) { + ERR_FAIL_COND(!p_data.has("width")); + ERR_FAIL_COND(!p_data.has("height")); + ERR_FAIL_COND(!p_data.has("format")); + ERR_FAIL_COND(!p_data.has("mipmaps")); + ERR_FAIL_COND(!p_data.has("data")); + + int dwidth = p_data["width"]; + int dheight = p_data["height"]; + String dformat = p_data["format"]; + bool dmipmaps = p_data["mipmaps"]; + Vector<uint8_t> ddata = p_data["data"]; + Format ddformat = FORMAT_MAX; + for (int i = 0; i < FORMAT_MAX; i++) { + if (dformat == get_format_name(Format(i))) { + ddformat = Format(i); + break; + } + } + + ERR_FAIL_COND(ddformat == FORMAT_MAX); + + create(dwidth, dheight, dmipmaps, ddformat, ddata); +} + +Dictionary Image::_get_data() const { + Dictionary d; + d["width"] = width; + d["height"] = height; + d["format"] = get_format_name(format); + d["mipmaps"] = mipmaps; + d["data"] = data; + return d; +} + +Color Image::get_pixelv(const Point2i &p_point) const { + return get_pixel(p_point.x, p_point.y); +} + +Color Image::_get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const { + switch (format) { + case FORMAT_L8: { + float l = ptr[ofs] / 255.0; + return Color(l, l, l, 1); + } + case FORMAT_LA8: { + float l = ptr[ofs * 2 + 0] / 255.0; + float a = ptr[ofs * 2 + 1] / 255.0; + return Color(l, l, l, a); + } + case FORMAT_R8: { + float r = ptr[ofs] / 255.0; + return Color(r, 0, 0, 1); + } + case FORMAT_RG8: { + float r = ptr[ofs * 2 + 0] / 255.0; + float g = ptr[ofs * 2 + 1] / 255.0; + return Color(r, g, 0, 1); + } + case FORMAT_RGB8: { + float r = ptr[ofs * 3 + 0] / 255.0; + float g = ptr[ofs * 3 + 1] / 255.0; + float b = ptr[ofs * 3 + 2] / 255.0; + return Color(r, g, b, 1); + } + case FORMAT_RGBA8: { + float r = ptr[ofs * 4 + 0] / 255.0; + float g = ptr[ofs * 4 + 1] / 255.0; + float b = ptr[ofs * 4 + 2] / 255.0; + float a = ptr[ofs * 4 + 3] / 255.0; + return Color(r, g, b, a); + } + case FORMAT_RGBA4444: { + uint16_t u = ((uint16_t *)ptr)[ofs]; + float r = ((u >> 12) & 0xF) / 15.0; + float g = ((u >> 8) & 0xF) / 15.0; + float b = ((u >> 4) & 0xF) / 15.0; + float a = (u & 0xF) / 15.0; + return Color(r, g, b, a); + } + case FORMAT_RGB565: { + uint16_t u = ((uint16_t *)ptr)[ofs]; + float r = (u & 0x1F) / 31.0; + float g = ((u >> 5) & 0x3F) / 63.0; + float b = ((u >> 11) & 0x1F) / 31.0; + return Color(r, g, b, 1.0); + } + case FORMAT_RF: { + float r = ((float *)ptr)[ofs]; + return Color(r, 0, 0, 1); + } + case FORMAT_RGF: { + float r = ((float *)ptr)[ofs * 2 + 0]; + float g = ((float *)ptr)[ofs * 2 + 1]; + return Color(r, g, 0, 1); + } + case FORMAT_RGBF: { + float r = ((float *)ptr)[ofs * 3 + 0]; + float g = ((float *)ptr)[ofs * 3 + 1]; + float b = ((float *)ptr)[ofs * 3 + 2]; + return Color(r, g, b, 1); + } + case FORMAT_RGBAF: { + float r = ((float *)ptr)[ofs * 4 + 0]; + float g = ((float *)ptr)[ofs * 4 + 1]; + float b = ((float *)ptr)[ofs * 4 + 2]; + float a = ((float *)ptr)[ofs * 4 + 3]; + return Color(r, g, b, a); + } + case FORMAT_RH: { + uint16_t r = ((uint16_t *)ptr)[ofs]; + return Color(Math::half_to_float(r), 0, 0, 1); + } + case FORMAT_RGH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 2 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 2 + 1]; + return Color(Math::half_to_float(r), Math::half_to_float(g), 0, 1); + } + case FORMAT_RGBH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 3 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 3 + 1]; + uint16_t b = ((uint16_t *)ptr)[ofs * 3 + 2]; + return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), 1); + } + case FORMAT_RGBAH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 4 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 4 + 1]; + uint16_t b = ((uint16_t *)ptr)[ofs * 4 + 2]; + uint16_t a = ((uint16_t *)ptr)[ofs * 4 + 3]; + return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), Math::half_to_float(a)); + } + case FORMAT_RGBE9995: { + return Color::from_rgbe9995(((uint32_t *)ptr)[ofs]); + } + default: { + ERR_FAIL_V_MSG(Color(), "Can't get_pixel() on compressed image, sorry."); + } + } +} + +void Image::_set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color) { + switch (format) { + case FORMAT_L8: { + ptr[ofs] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); + } break; + case FORMAT_LA8: { + ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); + ptr[ofs * 2 + 1] = uint8_t(CLAMP(p_color.a * 255.0, 0, 255)); + } break; + case FORMAT_R8: { + ptr[ofs] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + } break; + case FORMAT_RG8: { + ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 2 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + } break; + case FORMAT_RGB8: { + ptr[ofs * 3 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 3 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + ptr[ofs * 3 + 2] = uint8_t(CLAMP(p_color.b * 255.0, 0, 255)); + } break; + case FORMAT_RGBA8: { + ptr[ofs * 4 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 4 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + ptr[ofs * 4 + 2] = uint8_t(CLAMP(p_color.b * 255.0, 0, 255)); + ptr[ofs * 4 + 3] = uint8_t(CLAMP(p_color.a * 255.0, 0, 255)); + + } break; + case FORMAT_RGBA4444: { + uint16_t rgba = 0; + + rgba = uint16_t(CLAMP(p_color.r * 15.0, 0, 15)) << 12; + rgba |= uint16_t(CLAMP(p_color.g * 15.0, 0, 15)) << 8; + rgba |= uint16_t(CLAMP(p_color.b * 15.0, 0, 15)) << 4; + rgba |= uint16_t(CLAMP(p_color.a * 15.0, 0, 15)); + + ((uint16_t *)ptr)[ofs] = rgba; + + } break; + case FORMAT_RGB565: { + uint16_t rgba = 0; + + rgba = uint16_t(CLAMP(p_color.r * 31.0, 0, 31)); + rgba |= uint16_t(CLAMP(p_color.g * 63.0, 0, 33)) << 5; + rgba |= uint16_t(CLAMP(p_color.b * 31.0, 0, 31)) << 11; + + ((uint16_t *)ptr)[ofs] = rgba; + + } break; + case FORMAT_RF: { + ((float *)ptr)[ofs] = p_color.r; + } break; + case FORMAT_RGF: { + ((float *)ptr)[ofs * 2 + 0] = p_color.r; + ((float *)ptr)[ofs * 2 + 1] = p_color.g; + } break; + case FORMAT_RGBF: { + ((float *)ptr)[ofs * 3 + 0] = p_color.r; + ((float *)ptr)[ofs * 3 + 1] = p_color.g; + ((float *)ptr)[ofs * 3 + 2] = p_color.b; + } break; + case FORMAT_RGBAF: { + ((float *)ptr)[ofs * 4 + 0] = p_color.r; + ((float *)ptr)[ofs * 4 + 1] = p_color.g; + ((float *)ptr)[ofs * 4 + 2] = p_color.b; + ((float *)ptr)[ofs * 4 + 3] = p_color.a; + } break; + case FORMAT_RH: { + ((uint16_t *)ptr)[ofs] = Math::make_half_float(p_color.r); + } break; + case FORMAT_RGH: { + ((uint16_t *)ptr)[ofs * 2 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 2 + 1] = Math::make_half_float(p_color.g); + } break; + case FORMAT_RGBH: { + ((uint16_t *)ptr)[ofs * 3 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 3 + 1] = Math::make_half_float(p_color.g); + ((uint16_t *)ptr)[ofs * 3 + 2] = Math::make_half_float(p_color.b); + } break; + case FORMAT_RGBAH: { + ((uint16_t *)ptr)[ofs * 4 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 4 + 1] = Math::make_half_float(p_color.g); + ((uint16_t *)ptr)[ofs * 4 + 2] = Math::make_half_float(p_color.b); + ((uint16_t *)ptr)[ofs * 4 + 3] = Math::make_half_float(p_color.a); + } break; + case FORMAT_RGBE9995: { + ((uint32_t *)ptr)[ofs] = p_color.to_rgbe9995(); + + } break; + default: { + ERR_FAIL_MSG("Can't set_pixel() on compressed image, sorry."); + } + } +} + +Color Image::get_pixel(int p_x, int p_y) const { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(p_x, width, Color()); + ERR_FAIL_INDEX_V(p_y, height, Color()); +#endif + + uint32_t ofs = p_y * width + p_x; + return _get_color_at_ofs(data.ptr(), ofs); +} + +void Image::set_pixelv(const Point2i &p_point, const Color &p_color) { + set_pixel(p_point.x, p_point.y, p_color); +} + +void Image::set_pixel(int p_x, int p_y, const Color &p_color) { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX(p_x, width); + ERR_FAIL_INDEX(p_y, height); +#endif + + uint32_t ofs = p_y * width + p_x; + _set_color_at_ofs(data.ptrw(), ofs, p_color); +} + +void Image::adjust_bcs(float p_brightness, float p_contrast, float p_saturation) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot adjust_bcs in compressed or custom image formats."); + + uint8_t *w = data.ptrw(); + uint32_t pixel_size = get_format_pixel_size(format); + uint32_t pixel_count = data.size() / pixel_size; + + for (uint32_t i = 0; i < pixel_count; i++) { + Color c = _get_color_at_ofs(w, i); + Vector3 rgb(c.r, c.g, c.b); + + rgb *= p_brightness; + rgb = Vector3(0.5, 0.5, 0.5).lerp(rgb, p_contrast); + float center = (rgb.x + rgb.y + rgb.z) / 3.0; + rgb = Vector3(center, center, center).lerp(rgb, p_saturation); + c.r = rgb.x; + c.g = rgb.y; + c.b = rgb.z; + _set_color_at_ofs(w, i, c); + } +} + +Image::UsedChannels Image::detect_used_channels(CompressSource p_source) { + ERR_FAIL_COND_V(data.size() == 0, USED_CHANNELS_RGBA); + ERR_FAIL_COND_V(is_compressed(), USED_CHANNELS_RGBA); + bool r = false, g = false, b = false, a = false, c = false; + + const uint8_t *data_ptr = data.ptr(); + + uint32_t data_total = width * height; + + for (uint32_t i = 0; i < data_total; i++) { + Color col = _get_color_at_ofs(data_ptr, i); + + if (col.r > 0.001) { + r = true; + } + if (col.g > 0.001) { + g = true; + } + if (col.b > 0.001) { + b = true; + } + if (col.a < 0.999) { + a = true; + } + + if (col.r != col.b || col.r != col.g || col.b != col.g) { + c = true; + } + } + + UsedChannels used_channels; + + if (!c && !a) { + used_channels = USED_CHANNELS_L; + } else if (!c && a) { + used_channels = USED_CHANNELS_LA; + } else if (r && !g && !b && !a) { + used_channels = USED_CHANNELS_R; + } else if (r && g && !b && !a) { + used_channels = USED_CHANNELS_RG; + } else if (r && g && b && !a) { + used_channels = USED_CHANNELS_RGB; + } else { + used_channels = USED_CHANNELS_RGBA; + } + + if (p_source == COMPRESS_SOURCE_SRGB && (used_channels == USED_CHANNELS_R || used_channels == USED_CHANNELS_RG)) { + //R and RG do not support SRGB + used_channels = USED_CHANNELS_RGB; + } + + if (p_source == COMPRESS_SOURCE_NORMAL) { + //use RG channels only for normal + used_channels = USED_CHANNELS_RG; + } + + return used_channels; +} + +void Image::optimize_channels() { + switch (detect_used_channels()) { + case USED_CHANNELS_L: + convert(FORMAT_L8); + break; + case USED_CHANNELS_LA: + convert(FORMAT_LA8); + break; + case USED_CHANNELS_R: + convert(FORMAT_R8); + break; + case USED_CHANNELS_RG: + convert(FORMAT_RG8); + break; + case USED_CHANNELS_RGB: + convert(FORMAT_RGB8); + break; + case USED_CHANNELS_RGBA: + convert(FORMAT_RGBA8); + break; + } +} + +void Image::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_width"), &Image::get_width); + ClassDB::bind_method(D_METHOD("get_height"), &Image::get_height); + ClassDB::bind_method(D_METHOD("get_size"), &Image::get_size); + ClassDB::bind_method(D_METHOD("has_mipmaps"), &Image::has_mipmaps); + ClassDB::bind_method(D_METHOD("get_format"), &Image::get_format); + ClassDB::bind_method(D_METHOD("get_data"), &Image::get_data); + + ClassDB::bind_method(D_METHOD("convert", "format"), &Image::convert); + + ClassDB::bind_method(D_METHOD("get_mipmap_offset", "mipmap"), &Image::get_mipmap_offset); + + ClassDB::bind_method(D_METHOD("resize_to_po2", "square", "interpolation"), &Image::resize_to_po2, DEFVAL(false), DEFVAL(INTERPOLATE_BILINEAR)); + ClassDB::bind_method(D_METHOD("resize", "width", "height", "interpolation"), &Image::resize, DEFVAL(INTERPOLATE_BILINEAR)); + ClassDB::bind_method(D_METHOD("shrink_x2"), &Image::shrink_x2); + + ClassDB::bind_method(D_METHOD("crop", "width", "height"), &Image::crop); + ClassDB::bind_method(D_METHOD("flip_x"), &Image::flip_x); + ClassDB::bind_method(D_METHOD("flip_y"), &Image::flip_y); + ClassDB::bind_method(D_METHOD("generate_mipmaps", "renormalize"), &Image::generate_mipmaps, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("clear_mipmaps"), &Image::clear_mipmaps); + + ClassDB::bind_method(D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::_create_empty); + ClassDB::bind_method(D_METHOD("create_from_data", "width", "height", "use_mipmaps", "format", "data"), &Image::_create_from_data); + + ClassDB::bind_method(D_METHOD("is_empty"), &Image::is_empty); + + ClassDB::bind_method(D_METHOD("load", "path"), &Image::load); + ClassDB::bind_method(D_METHOD("save_png", "path"), &Image::save_png); + ClassDB::bind_method(D_METHOD("save_png_to_buffer"), &Image::save_png_to_buffer); + ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("detect_alpha"), &Image::detect_alpha); + ClassDB::bind_method(D_METHOD("is_invisible"), &Image::is_invisible); + + ClassDB::bind_method(D_METHOD("detect_used_channels", "source"), &Image::detect_used_channels, DEFVAL(COMPRESS_SOURCE_GENERIC)); + ClassDB::bind_method(D_METHOD("compress", "mode", "source", "lossy_quality"), &Image::compress, DEFVAL(COMPRESS_SOURCE_GENERIC), DEFVAL(0.7)); + ClassDB::bind_method(D_METHOD("compress_from_channels", "mode", "channels", "lossy_quality"), &Image::compress_from_channels, DEFVAL(0.7)); + ClassDB::bind_method(D_METHOD("decompress"), &Image::decompress); + ClassDB::bind_method(D_METHOD("is_compressed"), &Image::is_compressed); + + ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges); + ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); + ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); + ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy); + ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); + ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0)); + + ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect); + ClassDB::bind_method(D_METHOD("blit_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blit_rect_mask); + 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("get_used_rect"), &Image::get_used_rect); + ClassDB::bind_method(D_METHOD("get_rect", "rect"), &Image::get_rect); + + ClassDB::bind_method(D_METHOD("copy_from", "src"), &Image::copy_internals_from); + + ClassDB::bind_method(D_METHOD("_set_data", "data"), &Image::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &Image::_get_data); + + ClassDB::bind_method(D_METHOD("get_pixelv", "point"), &Image::get_pixelv); + ClassDB::bind_method(D_METHOD("get_pixel", "x", "y"), &Image::get_pixel); + ClassDB::bind_method(D_METHOD("set_pixelv", "point", "color"), &Image::set_pixelv); + ClassDB::bind_method(D_METHOD("set_pixel", "x", "y", "color"), &Image::set_pixel); + + ClassDB::bind_method(D_METHOD("adjust_bcs", "brightness", "contrast", "saturation"), &Image::adjust_bcs); + + ClassDB::bind_method(D_METHOD("load_png_from_buffer", "buffer"), &Image::load_png_from_buffer); + ClassDB::bind_method(D_METHOD("load_jpg_from_buffer", "buffer"), &Image::load_jpg_from_buffer); + ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer); + ClassDB::bind_method(D_METHOD("load_tga_from_buffer", "buffer"), &Image::load_tga_from_buffer); + ClassDB::bind_method(D_METHOD("load_bmp_from_buffer", "buffer"), &Image::load_bmp_from_buffer); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_data", "_get_data"); + + BIND_CONSTANT(MAX_WIDTH); + BIND_CONSTANT(MAX_HEIGHT); + + BIND_ENUM_CONSTANT(FORMAT_L8); //luminance + BIND_ENUM_CONSTANT(FORMAT_LA8); //luminance-alpha + BIND_ENUM_CONSTANT(FORMAT_R8); + BIND_ENUM_CONSTANT(FORMAT_RG8); + BIND_ENUM_CONSTANT(FORMAT_RGB8); + BIND_ENUM_CONSTANT(FORMAT_RGBA8); + BIND_ENUM_CONSTANT(FORMAT_RGBA4444); + BIND_ENUM_CONSTANT(FORMAT_RGB565); + BIND_ENUM_CONSTANT(FORMAT_RF); //float + BIND_ENUM_CONSTANT(FORMAT_RGF); + BIND_ENUM_CONSTANT(FORMAT_RGBF); + BIND_ENUM_CONSTANT(FORMAT_RGBAF); + BIND_ENUM_CONSTANT(FORMAT_RH); //half float + BIND_ENUM_CONSTANT(FORMAT_RGH); + BIND_ENUM_CONSTANT(FORMAT_RGBH); + BIND_ENUM_CONSTANT(FORMAT_RGBAH); + BIND_ENUM_CONSTANT(FORMAT_RGBE9995); + BIND_ENUM_CONSTANT(FORMAT_DXT1); //s3tc bc1 + BIND_ENUM_CONSTANT(FORMAT_DXT3); //bc2 + BIND_ENUM_CONSTANT(FORMAT_DXT5); //bc3 + BIND_ENUM_CONSTANT(FORMAT_RGTC_R); + BIND_ENUM_CONSTANT(FORMAT_RGTC_RG); + BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBA); //btpc bc6h + BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBF); //float / + BIND_ENUM_CONSTANT(FORMAT_BPTC_RGBFU); //unsigned float + BIND_ENUM_CONSTANT(FORMAT_PVRTC1_2); //pvrtc + BIND_ENUM_CONSTANT(FORMAT_PVRTC1_2A); + BIND_ENUM_CONSTANT(FORMAT_PVRTC1_4); + BIND_ENUM_CONSTANT(FORMAT_PVRTC1_4A); + BIND_ENUM_CONSTANT(FORMAT_ETC); //etc1 + BIND_ENUM_CONSTANT(FORMAT_ETC2_R11); //etc2 + BIND_ENUM_CONSTANT(FORMAT_ETC2_R11S); //signed ); NOT srgb. + BIND_ENUM_CONSTANT(FORMAT_ETC2_RG11); + BIND_ENUM_CONSTANT(FORMAT_ETC2_RG11S); + BIND_ENUM_CONSTANT(FORMAT_ETC2_RGB8); + BIND_ENUM_CONSTANT(FORMAT_ETC2_RGBA8); + BIND_ENUM_CONSTANT(FORMAT_ETC2_RGB8A1); + BIND_ENUM_CONSTANT(FORMAT_ETC2_RA_AS_RG); + BIND_ENUM_CONSTANT(FORMAT_DXT5_RA_AS_RG); + BIND_ENUM_CONSTANT(FORMAT_MAX); + + BIND_ENUM_CONSTANT(INTERPOLATE_NEAREST); + BIND_ENUM_CONSTANT(INTERPOLATE_BILINEAR); + BIND_ENUM_CONSTANT(INTERPOLATE_CUBIC); + BIND_ENUM_CONSTANT(INTERPOLATE_TRILINEAR); + BIND_ENUM_CONSTANT(INTERPOLATE_LANCZOS); + + BIND_ENUM_CONSTANT(ALPHA_NONE); + BIND_ENUM_CONSTANT(ALPHA_BIT); + BIND_ENUM_CONSTANT(ALPHA_BLEND); + + BIND_ENUM_CONSTANT(COMPRESS_S3TC); + BIND_ENUM_CONSTANT(COMPRESS_PVRTC1_4); + BIND_ENUM_CONSTANT(COMPRESS_ETC); + BIND_ENUM_CONSTANT(COMPRESS_ETC2); + BIND_ENUM_CONSTANT(COMPRESS_BPTC); + + BIND_ENUM_CONSTANT(USED_CHANNELS_L); + BIND_ENUM_CONSTANT(USED_CHANNELS_LA); + BIND_ENUM_CONSTANT(USED_CHANNELS_R); + BIND_ENUM_CONSTANT(USED_CHANNELS_RG); + BIND_ENUM_CONSTANT(USED_CHANNELS_RGB); + BIND_ENUM_CONSTANT(USED_CHANNELS_RGBA); + + BIND_ENUM_CONSTANT(COMPRESS_SOURCE_GENERIC); + BIND_ENUM_CONSTANT(COMPRESS_SOURCE_SRGB); + BIND_ENUM_CONSTANT(COMPRESS_SOURCE_NORMAL); +} + +void Image::set_compress_bc_func(void (*p_compress_func)(Image *, float, UsedChannels)) { + _image_compress_bc_func = p_compress_func; +} + +void Image::set_compress_bptc_func(void (*p_compress_func)(Image *, float, UsedChannels)) { + _image_compress_bptc_func = p_compress_func; +} + +void Image::normal_map_to_xy() { + convert(Image::FORMAT_RGBA8); + + { + int len = data.size() / 4; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 3] = data_ptr[(i << 2) + 0]; //x to w + data_ptr[(i << 2) + 0] = data_ptr[(i << 2) + 1]; //y to xz + data_ptr[(i << 2) + 2] = data_ptr[(i << 2) + 1]; + } + } + + convert(Image::FORMAT_LA8); +} + +Ref<Image> Image::rgbe_to_srgb() { + if (data.size() == 0) { + return Ref<Image>(); + } + + ERR_FAIL_COND_V(format != FORMAT_RGBE9995, Ref<Image>()); + + Ref<Image> new_image; + new_image.instantiate(); + new_image->create(width, height, false, Image::FORMAT_RGB8); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + new_image->set_pixel(col, row, get_pixel(col, row).to_srgb()); + } + } + + if (has_mipmaps()) { + new_image->generate_mipmaps(); + } + + return new_image; +} + +Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { + int ofs, size, w, h; + get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h); + + Vector<uint8_t> new_data; + new_data.resize(size); + + { + uint8_t *wr = new_data.ptrw(); + const uint8_t *rd = data.ptr(); + memcpy(wr, rd + ofs, size); + } + + Ref<Image> image; + image.instantiate(); + image->width = w; + image->height = h; + image->format = format; + image->data = new_data; + + image->mipmaps = false; + return image; +} + +void Image::bump_map_to_normal_map(float bump_scale) { + ERR_FAIL_COND(!_can_modify(format)); + convert(Image::FORMAT_RF); + + Vector<uint8_t> result_image; //rgba output + result_image.resize(width * height * 4); + + { + const uint8_t *rp = data.ptr(); + uint8_t *wp = result_image.ptrw(); + + ERR_FAIL_COND(!rp); + + unsigned char *write_ptr = wp; + float *read_ptr = (float *)rp; + + for (int ty = 0; ty < height; ty++) { + int py = ty + 1; + if (py >= height) { + py -= height; + } + + for (int tx = 0; tx < width; tx++) { + int px = tx + 1; + if (px >= width) { + px -= width; + } + float here = read_ptr[ty * width + tx]; + float to_right = read_ptr[ty * width + px]; + float above = read_ptr[py * width + tx]; + Vector3 up = Vector3(0, 1, (here - above) * bump_scale); + Vector3 across = Vector3(1, 0, (to_right - here) * bump_scale); + + Vector3 normal = across.cross(up); + normal.normalize(); + + write_ptr[((ty * width + tx) << 2) + 0] = (127.5 + normal.x * 127.5); + write_ptr[((ty * width + tx) << 2) + 1] = (127.5 + normal.y * 127.5); + write_ptr[((ty * width + tx) << 2) + 2] = (127.5 + normal.z * 127.5); + write_ptr[((ty * width + tx) << 2) + 3] = 255; + } + } + } + format = FORMAT_RGBA8; + data = result_image; +} + +void Image::srgb_to_linear() { + if (data.size() == 0) { + return; + } + + static const uint8_t srgb2lin[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51, 52, 53, 54, 55, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 92, 93, 94, 95, 97, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 112, 113, 114, 116, 117, 119, 120, 122, 123, 125, 126, 128, 129, 131, 132, 134, 135, 137, 139, 140, 142, 144, 145, 147, 148, 150, 152, 153, 155, 157, 159, 160, 162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 235, 237, 239, 241, 243, 245, 248, 250, 252, 255 }; + + ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8); + + if (format == FORMAT_RGBA8) { + int len = data.size() / 4; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 0] = srgb2lin[data_ptr[(i << 2) + 0]]; + data_ptr[(i << 2) + 1] = srgb2lin[data_ptr[(i << 2) + 1]]; + data_ptr[(i << 2) + 2] = srgb2lin[data_ptr[(i << 2) + 2]]; + } + + } else if (format == FORMAT_RGB8) { + int len = data.size() / 3; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i * 3) + 0] = srgb2lin[data_ptr[(i * 3) + 0]]; + data_ptr[(i * 3) + 1] = srgb2lin[data_ptr[(i * 3) + 1]]; + data_ptr[(i * 3) + 2] = srgb2lin[data_ptr[(i * 3) + 2]]; + } + } +} + +void Image::premultiply_alpha() { + if (data.size() == 0) { + return; + } + + if (format != FORMAT_RGBA8) { + return; //not needed + } + + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + uint8_t *ptr = &data_ptr[(i * width + j) * 4]; + + ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3])) >> 8; + ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3])) >> 8; + ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3])) >> 8; + } + } +} + +void Image::fix_alpha_edges() { + if (data.size() == 0) { + return; + } + + if (format != FORMAT_RGBA8) { + return; //not needed + } + + Vector<uint8_t> dcopy = data; + const uint8_t *srcptr = dcopy.ptr(); + + uint8_t *data_ptr = data.ptrw(); + + const int max_radius = 4; + const int alpha_threshold = 20; + const int max_dist = 0x7FFFFFFF; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + const uint8_t *rptr = &srcptr[(i * width + j) * 4]; + uint8_t *wptr = &data_ptr[(i * width + j) * 4]; + + if (rptr[3] >= alpha_threshold) { + continue; + } + + int closest_dist = max_dist; + uint8_t closest_color[3]; + + int from_x = MAX(0, j - max_radius); + int to_x = MIN(width - 1, j + max_radius); + int from_y = MAX(0, i - max_radius); + int to_y = MIN(height - 1, i + max_radius); + + for (int k = from_y; k <= to_y; k++) { + for (int l = from_x; l <= to_x; l++) { + int dy = i - k; + int dx = j - l; + int dist = dy * dy + dx * dx; + if (dist >= closest_dist) { + continue; + } + + const uint8_t *rp2 = &srcptr[(k * width + l) << 2]; + + if (rp2[3] < alpha_threshold) { + continue; + } + + closest_dist = dist; + closest_color[0] = rp2[0]; + closest_color[1] = rp2[1]; + closest_color[2] = rp2[2]; + } + } + + if (closest_dist != max_dist) { + wptr[0] = closest_color[0]; + wptr[1] = closest_color[1]; + wptr[2] = closest_color[2]; + } + } + } +} + +String Image::get_format_name(Format p_format) { + ERR_FAIL_INDEX_V(p_format, FORMAT_MAX, String()); + return format_names[p_format]; +} + +Error Image::load_png_from_buffer(const Vector<uint8_t> &p_array) { + return _load_from_buffer(p_array, _png_mem_loader_func); +} + +Error Image::load_jpg_from_buffer(const Vector<uint8_t> &p_array) { + return _load_from_buffer(p_array, _jpg_mem_loader_func); +} + +Error Image::load_webp_from_buffer(const Vector<uint8_t> &p_array) { + return _load_from_buffer(p_array, _webp_mem_loader_func); +} + +Error Image::load_tga_from_buffer(const Vector<uint8_t> &p_array) { + ERR_FAIL_NULL_V_MSG( + _tga_mem_loader_func, + ERR_UNAVAILABLE, + "The TGA module isn't enabled. Recompile the Godot editor or export template binary with the `module_tga_enabled=yes` SCons option."); + return _load_from_buffer(p_array, _tga_mem_loader_func); +} + +Error Image::load_bmp_from_buffer(const Vector<uint8_t> &p_array) { + ERR_FAIL_NULL_V_MSG( + _bmp_mem_loader_func, + ERR_UNAVAILABLE, + "The BMP module isn't enabled. Recompile the Godot editor or export template binary with the `module_bmp_enabled=yes` SCons option."); + return _load_from_buffer(p_array, _bmp_mem_loader_func); +} + +void Image::convert_rg_to_ra_rgba8() { + ERR_FAIL_COND(format != FORMAT_RGBA8); + ERR_FAIL_COND(!data.size()); + + int s = data.size(); + uint8_t *w = data.ptrw(); + for (int i = 0; i < s; i += 4) { + w[i + 3] = w[i + 1]; + w[i + 1] = 0; + w[i + 2] = 0; + } +} + +void Image::convert_ra_rgba8_to_rg() { + ERR_FAIL_COND(format != FORMAT_RGBA8); + ERR_FAIL_COND(!data.size()); + + int s = data.size(); + uint8_t *w = data.ptrw(); + for (int i = 0; i < s; i += 4) { + w[i + 1] = w[i + 3]; + w[i + 2] = 0; + w[i + 3] = 255; + } +} + +Error Image::_load_from_buffer(const Vector<uint8_t> &p_array, ImageMemLoadFunc p_loader) { + int buffer_size = p_array.size(); + + ERR_FAIL_COND_V(buffer_size == 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!p_loader, ERR_INVALID_PARAMETER); + + const uint8_t *r = p_array.ptr(); + + Ref<Image> image = p_loader(r, buffer_size); + ERR_FAIL_COND_V(!image.is_valid(), ERR_PARSE_ERROR); + + copy_internals_from(image); + + return OK; +} + +void Image::average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d) { + p_out = static_cast<uint8_t>((p_a + p_b + p_c + p_d + 2) >> 2); +} + +void Image::average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d) { + p_out = (p_a + p_b + p_c + p_d) * 0.25f; +} + +void Image::average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d) { + p_out = Math::make_half_float((Math::half_to_float(p_a) + Math::half_to_float(p_b) + Math::half_to_float(p_c) + Math::half_to_float(p_d)) * 0.25f); +} + +void Image::average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d) { + p_out = ((Color::from_rgbe9995(p_a) + Color::from_rgbe9995(p_b) + Color::from_rgbe9995(p_c) + Color::from_rgbe9995(p_d)) * 0.25f).to_rgbe9995(); +} + +void Image::renormalize_uint8(uint8_t *p_rgb) { + Vector3 n(p_rgb[0] / 255.0, p_rgb[1] / 255.0, p_rgb[2] / 255.0); + n *= 2.0; + n -= Vector3(1, 1, 1); + n.normalize(); + n += Vector3(1, 1, 1); + n *= 0.5; + n *= 255; + p_rgb[0] = CLAMP(int(n.x), 0, 255); + p_rgb[1] = CLAMP(int(n.y), 0, 255); + p_rgb[2] = CLAMP(int(n.z), 0, 255); +} + +void Image::renormalize_float(float *p_rgb) { + Vector3 n(p_rgb[0], p_rgb[1], p_rgb[2]); + n.normalize(); + p_rgb[0] = n.x; + p_rgb[1] = n.y; + p_rgb[2] = n.z; +} + +void Image::renormalize_half(uint16_t *p_rgb) { + Vector3 n(Math::half_to_float(p_rgb[0]), Math::half_to_float(p_rgb[1]), Math::half_to_float(p_rgb[2])); + n.normalize(); + p_rgb[0] = Math::make_half_float(n.x); + p_rgb[1] = Math::make_half_float(n.y); + p_rgb[2] = Math::make_half_float(n.z); +} + +void Image::renormalize_rgbe9995(uint32_t *p_rgb) { + // Never used +} + +Image::Image(const uint8_t *p_mem_png_jpg, int p_len) { + width = 0; + height = 0; + mipmaps = false; + format = FORMAT_L8; + + if (_png_mem_loader_func) { + copy_internals_from(_png_mem_loader_func(p_mem_png_jpg, p_len)); + } + + if (is_empty() && _jpg_mem_loader_func) { + copy_internals_from(_jpg_mem_loader_func(p_mem_png_jpg, p_len)); + } +} + +Ref<Resource> Image::duplicate(bool p_subresources) const { + Ref<Image> copy; + copy.instantiate(); + copy->_copy_internals_from(*this); + return copy; +} + +void Image::set_as_black() { + memset(data.ptrw(), 0, data.size()); +} diff --git a/core/io/image.h b/core/io/image.h new file mode 100644 index 0000000000..8f1b251ac3 --- /dev/null +++ b/core/io/image.h @@ -0,0 +1,418 @@ +/*************************************************************************/ +/* image.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 IMAGE_H +#define IMAGE_H + +#include "core/io/resource.h" +#include "core/math/color.h" +#include "core/math/rect2.h" + +/** + * @author Juan Linietsky <reduzio@gmail.com> + * + * 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; + +typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img); +typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img); +typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size); + +typedef Error (*SaveEXRFunc)(const String &p_path, const Ref<Image> &p_img, bool p_grayscale); + +class Image : public Resource { + GDCLASS(Image, Resource); + +public: + static SavePNGFunc save_png_func; + static SaveEXRFunc save_exr_func; + static SavePNGBufferFunc save_png_buffer_func; + + enum { + MAX_WIDTH = (1 << 24), // force a limit somehow + MAX_HEIGHT = (1 << 24), // force a limit somehow + MAX_PIXELS = 268435456 + }; + + enum Format { + FORMAT_L8, //luminance + FORMAT_LA8, //luminance-alpha + FORMAT_R8, + FORMAT_RG8, + FORMAT_RGB8, + FORMAT_RGBA8, + FORMAT_RGBA4444, + FORMAT_RGB565, + FORMAT_RF, //float + FORMAT_RGF, + FORMAT_RGBF, + FORMAT_RGBAF, + FORMAT_RH, //half float + FORMAT_RGH, + FORMAT_RGBH, + FORMAT_RGBAH, + FORMAT_RGBE9995, + FORMAT_DXT1, //s3tc bc1 + FORMAT_DXT3, //bc2 + FORMAT_DXT5, //bc3 + FORMAT_RGTC_R, + FORMAT_RGTC_RG, + FORMAT_BPTC_RGBA, //btpc bc7 + FORMAT_BPTC_RGBF, //float bc6h + FORMAT_BPTC_RGBFU, //unsigned float bc6hu + FORMAT_PVRTC1_2, //pvrtc1 + FORMAT_PVRTC1_2A, + FORMAT_PVRTC1_4, + FORMAT_PVRTC1_4A, + FORMAT_ETC, //etc1 + FORMAT_ETC2_R11, //etc2 + FORMAT_ETC2_R11S, //signed, NOT srgb. + FORMAT_ETC2_RG11, + FORMAT_ETC2_RG11S, + FORMAT_ETC2_RGB8, + FORMAT_ETC2_RGBA8, + FORMAT_ETC2_RGB8A1, + FORMAT_ETC2_RA_AS_RG, //used to make basis universal happy + FORMAT_DXT5_RA_AS_RG, //used to make basis universal happy + FORMAT_MAX + }; + + static const char *format_names[FORMAT_MAX]; + enum Interpolation { + INTERPOLATE_NEAREST, + INTERPOLATE_BILINEAR, + INTERPOLATE_CUBIC, + INTERPOLATE_TRILINEAR, + INTERPOLATE_LANCZOS, + /* INTERPOLATE_TRICUBIC, */ + /* INTERPOLATE GAUSS */ + }; + + //this is used for compression + enum UsedChannels { + USED_CHANNELS_L, + USED_CHANNELS_LA, + USED_CHANNELS_R, + USED_CHANNELS_RG, + USED_CHANNELS_RGB, + USED_CHANNELS_RGBA, + }; + //some functions provided by something else + + static ImageMemLoadFunc _png_mem_loader_func; + static ImageMemLoadFunc _jpg_mem_loader_func; + static ImageMemLoadFunc _webp_mem_loader_func; + static ImageMemLoadFunc _tga_mem_loader_func; + static ImageMemLoadFunc _bmp_mem_loader_func; + + static void (*_image_compress_bc_func)(Image *, float, UsedChannels p_channels); + static void (*_image_compress_bptc_func)(Image *, float p_lossy_quality, UsedChannels p_channels); + static void (*_image_compress_pvrtc1_4bpp_func)(Image *); + static void (*_image_compress_etc1_func)(Image *, float); + static void (*_image_compress_etc2_func)(Image *, float, UsedChannels p_channels); + + static void (*_image_decompress_pvrtc)(Image *); + static void (*_image_decompress_bc)(Image *); + static void (*_image_decompress_bptc)(Image *); + static void (*_image_decompress_etc1)(Image *); + static void (*_image_decompress_etc2)(Image *); + + static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality); + static Vector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image); + static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer); + static Vector<uint8_t> (*png_packer)(const Ref<Image> &p_image); + static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer); + static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels); + static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer); + + _FORCE_INLINE_ Color _get_color_at_ofs(const uint8_t *ptr, uint32_t ofs) const; + _FORCE_INLINE_ void _set_color_at_ofs(uint8_t *ptr, uint32_t ofs, const Color &p_color); + +protected: + static void _bind_methods(); + +private: + void _create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + create(p_width, p_height, p_use_mipmaps, p_format); + } + + void _create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) { + create(p_width, p_height, p_use_mipmaps, p_format, p_data); + } + + Format format = FORMAT_L8; + Vector<uint8_t> data; + int width = 0; + int height = 0; + bool mipmaps = false; + + void _copy_internals_from(const Image &p_image) { + format = p_image.format; + width = p_image.width; + height = p_image.height; + mipmaps = p_image.mipmaps; + data = p_image.data; + } + + _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data + + 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); + + void _set_data(const Dictionary &p_data); + Dictionary _get_data() const; + + Error _load_from_buffer(const Vector<uint8_t> &p_array, ImageMemLoadFunc p_loader); + + static void average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d); + static void average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d); + static void average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d); + static void average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d); + static void renormalize_uint8(uint8_t *p_rgb); + static void renormalize_float(float *p_rgb); + static void renormalize_half(uint16_t *p_rgb); + static void renormalize_rgbe9995(uint32_t *p_rgb); + +public: + int get_width() const; ///< Get image width + int get_height() const; ///< Get image height + Vector2 get_size() const; + bool has_mipmaps() const; + int get_mipmap_count() const; + + /** + * Convert the image to another format, conversion only to raw byte format + */ + void convert(Format p_new_format); + + /** + * Get the current image format. + */ + Format get_format() const; + + int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data + int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data + void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data + void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data + + enum Image3DValidateError { + VALIDATE_3D_OK, + VALIDATE_3D_ERR_IMAGE_EMPTY, + VALIDATE_3D_ERR_MISSING_IMAGES, + VALIDATE_3D_ERR_EXTRA_IMAGES, + VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH, + VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH, + VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS, + }; + + static Image3DValidateError validate_3d_image(Format p_format, int p_width, int p_height, int p_depth, bool p_mipmaps, const Vector<Ref<Image>> &p_images); + static String get_3d_image_validation_error_text(Image3DValidateError p_error); + + /** + * Resize the image, using the preferred interpolation method. + */ + void resize_to_po2(bool p_square = false, Interpolation p_interpolation = INTERPOLATE_BILINEAR); + void resize(int p_width, int p_height, Interpolation p_interpolation = INTERPOLATE_BILINEAR); + void shrink_x2(); + bool is_size_po2() const; + /** + * Crop the image to a specific size, if larger, then the image is filled by black + */ + void crop_from_point(int p_x, int p_y, int p_width, int p_height); + void crop(int p_width, int p_height); + + void flip_x(); + void flip_y(); + + /** + * Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1) + */ + Error generate_mipmaps(bool p_renormalize = false); + + enum RoughnessChannel { + ROUGHNESS_CHANNEL_R, + ROUGHNESS_CHANNEL_G, + ROUGHNESS_CHANNEL_B, + ROUGHNESS_CHANNEL_A, + ROUGHNESS_CHANNEL_L, + }; + + Error generate_mipmap_roughness(RoughnessChannel p_roughness_channel, const Ref<Image> &p_normal_map); + + void clear_mipmaps(); + void normalize(); //for normal maps + + /** + * Create a new image of a given size and format. Current image will be lost + */ + void create(int p_width, int p_height, bool p_use_mipmaps, Format p_format); + void create(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data); + + void create(const char **p_xpm); + /** + * returns true when the image is empty (0,0) in size + */ + bool is_empty() const; + + Vector<uint8_t> get_data() const; + + Error load(const String &p_path); + Error save_png(const String &p_path) const; + Vector<uint8_t> save_png_to_buffer() const; + Error save_exr(const String &p_path, bool p_grayscale) const; + + /** + * create an empty image + */ + Image() {} + /** + * create an empty image of a specific size and format + */ + Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format); + /** + * import an image of a specific size and format from a pointer + */ + Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data); + + ~Image() {} + + enum AlphaMode { + ALPHA_NONE, + ALPHA_BIT, + ALPHA_BLEND + }; + + AlphaMode detect_alpha() const; + bool is_invisible() const; + + static int get_format_pixel_size(Format p_format); + static int get_format_pixel_rshift(Format p_format); + static int get_format_block_size(Format p_format); + static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h); + + static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); + static int get_image_required_mipmaps(int p_width, int p_height, Format p_format); + static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap); + static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); + static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); + + enum CompressMode { + COMPRESS_S3TC, + COMPRESS_PVRTC1_4, + COMPRESS_ETC, + COMPRESS_ETC2, + COMPRESS_BPTC, + COMPRESS_MAX, + }; + enum CompressSource { + COMPRESS_SOURCE_GENERIC, + COMPRESS_SOURCE_SRGB, + COMPRESS_SOURCE_NORMAL, + COMPRESS_SOURCE_MAX, + }; + + Error compress(CompressMode p_mode, CompressSource p_source = COMPRESS_SOURCE_GENERIC, float p_lossy_quality = 0.7); + Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality = 0.7); + Error decompress(); + bool is_compressed() const; + + void fix_alpha_edges(); + void premultiply_alpha(); + void srgb_to_linear(); + void normal_map_to_xy(); + Ref<Image> rgbe_to_srgb(); + Ref<Image> get_image_from_mipmap(int p_mipamp) const; + void bump_map_to_normal_map(float bump_scale = 1.0); + + void blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest); + 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); + + Rect2 get_used_rect() const; + Ref<Image> get_rect(const Rect2 &p_area) const; + + static void set_compress_bc_func(void (*p_compress_func)(Image *, float, UsedChannels)); + static void set_compress_bptc_func(void (*p_compress_func)(Image *, float, UsedChannels)); + static String get_format_name(Format p_format); + + Error load_png_from_buffer(const Vector<uint8_t> &p_array); + Error load_jpg_from_buffer(const Vector<uint8_t> &p_array); + Error load_webp_from_buffer(const Vector<uint8_t> &p_array); + Error load_tga_from_buffer(const Vector<uint8_t> &p_array); + Error load_bmp_from_buffer(const Vector<uint8_t> &p_array); + + void convert_rg_to_ra_rgba8(); + void convert_ra_rgba8_to_rg(); + + Image(const uint8_t *p_mem_png_jpg, int p_len = -1); + Image(const char **p_xpm); + + virtual Ref<Resource> duplicate(bool p_subresources = false) const override; + + UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC); + void optimize_channels(); + + Color get_pixelv(const Point2i &p_point) const; + Color get_pixel(int p_x, int p_y) const; + void set_pixelv(const Point2i &p_point, const Color &p_color); + void set_pixel(int p_x, int p_y, const Color &p_color); + + void adjust_bcs(float p_brightness, float p_contrast, float p_saturation); + + void set_as_black(); + + void copy_internals_from(const Ref<Image> &p_image) { + ERR_FAIL_COND_MSG(p_image.is_null(), "It's not a reference to a valid Image object."); + format = p_image->format; + width = p_image->width; + height = p_image->height; + mipmaps = p_image->mipmaps; + data = p_image->data; + } +}; + +VARIANT_ENUM_CAST(Image::Format) +VARIANT_ENUM_CAST(Image::Interpolation) +VARIANT_ENUM_CAST(Image::CompressMode) +VARIANT_ENUM_CAST(Image::CompressSource) +VARIANT_ENUM_CAST(Image::UsedChannels) +VARIANT_ENUM_CAST(Image::AlphaMode) +VARIANT_ENUM_CAST(Image::RoughnessChannel) + +#endif // IMAGE_H diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp index b1e92eb87f..b9fc416f65 100644 --- a/core/io/image_loader.cpp +++ b/core/io/image_loader.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,13 +30,13 @@ #include "image_loader.h" -#include "core/print_string.h" +#include "core/string/print_string.h" bool ImageFormatLoader::recognize(const String &p_extension) const { List<String> extensions; get_recognized_extensions(&extensions); - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - if (E->get().nocasecmp_to(p_extension) == 0) { + for (const String &E : extensions) { + if (E.nocasecmp_to(p_extension) == 0) { return true; } } @@ -122,7 +122,7 @@ void ImageLoader::cleanup() { ///////////////// -RES ResourceFormatLoaderImage::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderImage::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { if (r_error) { @@ -163,7 +163,7 @@ RES ResourceFormatLoaderImage::load(const String &p_path, const String &p_origin } Ref<Image> image; - image.instance(); + image.instantiate(); Error err = ImageLoader::loader[idx]->load_image(image, f, false, 1.0); diff --git a/core/io/image_loader.h b/core/io/image_loader.h index 9682f144c7..6d1b1e3646 100644 --- a/core/io/image_loader.h +++ b/core/io/image_loader.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,11 +31,11 @@ #ifndef IMAGE_LOADER_H #define IMAGE_LOADER_H -#include "core/image.h" +#include "core/io/file_access.h" +#include "core/io/image.h" #include "core/io/resource_loader.h" -#include "core/list.h" -#include "core/os/file_access.h" -#include "core/ustring.h" +#include "core/string/ustring.h" +#include "core/templates/list.h" class ImageLoader; @@ -72,7 +72,7 @@ public: class ResourceFormatLoaderImage : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/core/io/ip.cpp b/core/io/ip.cpp index 24b8ec7cc1..e3102508a3 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,9 @@ #include "ip.h" -#include "core/hash_map.h" #include "core/os/semaphore.h" #include "core/os/thread.h" +#include "core/templates/hash_map.h" VARIANT_ENUM_CAST(IP::ResolverStatus); @@ -40,14 +40,16 @@ VARIANT_ENUM_CAST(IP::ResolverStatus); struct _IP_ResolverPrivate { struct QueueItem { - volatile IP::ResolverStatus status; - IP_Address response; + SafeNumeric<IP::ResolverStatus> status; + + List<IPAddress> response; + String hostname; IP::Type type; void clear() { - status = IP::RESOLVER_STATUS_NONE; - response = IP_Address(); + status.set(IP::RESOLVER_STATUS_NONE); + response.clear(); type = IP::TYPE_NONE; hostname = ""; }; @@ -61,7 +63,7 @@ struct _IP_ResolverPrivate { IP::ResolverID find_empty_id() const { for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) { - if (queue[i].status == IP::RESOLVER_STATUS_NONE) { + if (queue[i].status.get() == IP::RESOLVER_STATUS_NONE) { return i; } } @@ -71,22 +73,33 @@ struct _IP_ResolverPrivate { Mutex mutex; Semaphore sem; - Thread *thread; + Thread thread; //Semaphore* semaphore; bool thread_abort; void resolve_queues() { for (int i = 0; i < IP::RESOLVER_MAX_QUERIES; i++) { - if (queue[i].status != IP::RESOLVER_STATUS_WAITING) { + if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { continue; } - queue[i].response = IP::get_singleton()->resolve_hostname(queue[i].hostname, queue[i].type); - if (!queue[i].response.is_valid()) { - queue[i].status = IP::RESOLVER_STATUS_ERROR; - } else { - queue[i].status = IP::RESOLVER_STATUS_DONE; + mutex.lock(); + List<IPAddress> response; + String hostname = queue[i].hostname; + IP::Type type = queue[i].type; + mutex.unlock(); + + // We should not lock while resolving the hostname, + // only when modifying the queue. + IP::get_singleton()->_resolve_hostname(response, hostname, type); + + MutexLock lock(mutex); + // Could have been completed by another function, or deleted. + if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { + continue; } + queue[i].response = response; + queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE); } } @@ -95,31 +108,70 @@ struct _IP_ResolverPrivate { while (!ipr->thread_abort) { ipr->sem.wait(); - - MutexLock lock(ipr->mutex); ipr->resolve_queues(); } } - HashMap<String, IP_Address> cache; + HashMap<String, List<IPAddress>> cache; static String get_cache_key(String p_hostname, IP::Type p_type) { return itos(p_type) + p_hostname; } }; -IP_Address IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { - MutexLock lock(resolver->mutex); +IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) { + List<IPAddress> res; + String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); + + resolver->mutex.lock(); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep + // resolving other requests. + resolver->mutex.unlock(); + _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). + resolver->cache[key] = res; + } + resolver->mutex.unlock(); + for (int i = 0; i < res.size(); ++i) { + if (res[i].is_valid()) { + return res[i]; + } + } + return IPAddress(); +} + +Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) { + List<IPAddress> res; String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - if (resolver->cache.has(key) && resolver->cache[key].is_valid()) { - IP_Address res = resolver->cache[key]; - return res; + + resolver->mutex.lock(); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep resolving + // other requests. + resolver->mutex.unlock(); + _resolve_hostname(res, p_hostname, p_type); + resolver->mutex.lock(); + // We might be overriding another result, but we don't care (they are the + // same hostname). + resolver->cache[key] = res; } + resolver->mutex.unlock(); - IP_Address res = _resolve_hostname(p_hostname, p_type); - resolver->cache[key] = res; - return res; + Array result; + for (int i = 0; i < res.size(); ++i) { + if (res[i].is_valid()) { + result.push_back(String(res[i])); + } + } + return result; } IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Type p_type) { @@ -135,13 +187,13 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); resolver->queue[id].hostname = p_hostname; resolver->queue[id].type = p_type; - if (resolver->cache.has(key) && resolver->cache[key].is_valid()) { + if (resolver->cache.has(key)) { resolver->queue[id].response = resolver->cache[key]; - resolver->queue[id].status = IP::RESOLVER_STATUS_DONE; + resolver->queue[id].status.set(IP::RESOLVER_STATUS_DONE); } else { - resolver->queue[id].response = IP_Address(); - resolver->queue[id].status = IP::RESOLVER_STATUS_WAITING; - if (resolver->thread) { + resolver->queue[id].response = List<IPAddress>(); + resolver->queue[id].status.set(IP::RESOLVER_STATUS_WAITING); + if (resolver->thread.is_started()) { resolver->sem.post(); } else { resolver->resolve_queues(); @@ -154,42 +206,64 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const { ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE); - MutexLock lock(resolver->mutex); - - if (resolver->queue[p_id].status == IP::RESOLVER_STATUS_NONE) { + IP::ResolverStatus res = resolver->queue[p_id].status.get(); + if (res == IP::RESOLVER_STATUS_NONE) { ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE"); - resolver->mutex.unlock(); return IP::RESOLVER_STATUS_NONE; } - return resolver->queue[p_id].status; + return res; } -IP_Address IP::get_resolve_item_address(ResolverID p_id) const { - ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP_Address()); +IPAddress IP::get_resolve_item_address(ResolverID p_id) const { + ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IPAddress()); MutexLock lock(resolver->mutex); - if (resolver->queue[p_id].status != IP::RESOLVER_STATUS_DONE) { + if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) { ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet."); - resolver->mutex.unlock(); - return IP_Address(); + return IPAddress(); } - return resolver->queue[p_id].response; + List<IPAddress> res = resolver->queue[p_id].response; + + for (int i = 0; i < res.size(); ++i) { + if (res[i].is_valid()) { + return res[i]; + } + } + return IPAddress(); +} + +Array IP::get_resolve_item_addresses(ResolverID p_id) const { + ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, Array()); + MutexLock lock(resolver->mutex); + + if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) { + ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet."); + return Array(); + } + + List<IPAddress> res = resolver->queue[p_id].response; + + Array result; + for (int i = 0; i < res.size(); ++i) { + if (res[i].is_valid()) { + result.push_back(String(res[i])); + } + } + return result; } void IP::erase_resolve_item(ResolverID p_id) { ERR_FAIL_INDEX(p_id, IP::RESOLVER_MAX_QUERIES); - MutexLock lock(resolver->mutex); - - resolver->queue[p_id].status = IP::RESOLVER_STATUS_NONE; + resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE); } void IP::clear_cache(const String &p_hostname) { MutexLock lock(resolver->mutex); - if (p_hostname.empty()) { + if (p_hostname.is_empty()) { resolver->cache.clear(); } else { resolver->cache.erase(_IP_ResolverPrivate::get_cache_key(p_hostname, IP::TYPE_NONE)); @@ -201,10 +275,10 @@ void IP::clear_cache(const String &p_hostname) { Array IP::_get_local_addresses() const { Array addresses; - List<IP_Address> ip_addresses; + List<IPAddress> ip_addresses; get_local_addresses(&ip_addresses); - for (List<IP_Address>::Element *E = ip_addresses.front(); E; E = E->next()) { - addresses.push_back(E->get()); + for (const IPAddress &E : ip_addresses) { + addresses.push_back(E); } return addresses; @@ -222,8 +296,8 @@ Array IP::_get_local_interfaces() const { rc["index"] = c.index; Array ips; - for (const List<IP_Address>::Element *F = c.ip_addresses.front(); F; F = F->next()) { - ips.push_front(F->get()); + for (const IPAddress &F : c.ip_addresses) { + ips.push_front(F); } rc["addresses"] = ips; @@ -233,21 +307,23 @@ Array IP::_get_local_interfaces() const { return results; } -void IP::get_local_addresses(List<IP_Address> *r_addresses) 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 List<IP_Address>::Element *F = E->get().ip_addresses.front(); F; F = F->next()) { - r_addresses->push_front(F->get()); + for (const IPAddress &F : E->get().ip_addresses) { + r_addresses->push_front(F); } } } void IP::_bind_methods() { ClassDB::bind_method(D_METHOD("resolve_hostname", "host", "ip_type"), &IP::resolve_hostname, DEFVAL(IP::TYPE_ANY)); + ClassDB::bind_method(D_METHOD("resolve_hostname_addresses", "host", "ip_type"), &IP::resolve_hostname_addresses, DEFVAL(IP::TYPE_ANY)); ClassDB::bind_method(D_METHOD("resolve_hostname_queue_item", "host", "ip_type"), &IP::resolve_hostname_queue_item, DEFVAL(IP::TYPE_ANY)); ClassDB::bind_method(D_METHOD("get_resolve_item_status", "id"), &IP::get_resolve_item_status); ClassDB::bind_method(D_METHOD("get_resolve_item_address", "id"), &IP::get_resolve_item_address); + ClassDB::bind_method(D_METHOD("get_resolve_item_addresses", "id"), &IP::get_resolve_item_addresses); ClassDB::bind_method(D_METHOD("erase_resolve_item", "id"), &IP::erase_resolve_item); ClassDB::bind_method(D_METHOD("get_local_addresses"), &IP::_get_local_addresses); ClassDB::bind_method(D_METHOD("get_local_interfaces"), &IP::_get_local_interfaces); @@ -285,26 +361,14 @@ IP::IP() { singleton = this; resolver = memnew(_IP_ResolverPrivate); -#ifndef NO_THREADS - resolver->thread_abort = false; - - resolver->thread = Thread::create(_IP_ResolverPrivate::_thread_function, resolver); -#else - resolver->thread = nullptr; -#endif + resolver->thread.start(_IP_ResolverPrivate::_thread_function, resolver); } IP::~IP() { -#ifndef NO_THREADS - if (resolver->thread) { - resolver->thread_abort = true; - resolver->sem.post(); - Thread::wait_to_finish(resolver->thread); - memdelete(resolver->thread); - } - -#endif + resolver->thread_abort = true; + resolver->sem.post(); + resolver->thread.wait_to_finish(); memdelete(resolver); } diff --git a/core/io/ip.h b/core/io/ip.h index d434d02f9b..3c6040a1f0 100644 --- a/core/io/ip.h +++ b/core/io/ip.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,7 +42,6 @@ class IP : public Object { public: enum ResolverStatus { - RESOLVER_STATUS_NONE, RESOLVER_STATUS_WAITING, RESOLVER_STATUS_DONE, @@ -50,7 +49,6 @@ public: }; enum Type { - TYPE_NONE = 0, TYPE_IPV4 = 1, TYPE_IPV6 = 2, @@ -71,7 +69,6 @@ protected: static IP *singleton; static void _bind_methods(); - virtual IP_Address _resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY) = 0; Array _get_local_addresses() const; Array _get_local_interfaces() const; @@ -82,15 +79,20 @@ public: String name; String name_friendly; String index; - List<IP_Address> ip_addresses; + List<IPAddress> ip_addresses; }; - IP_Address resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY); + IPAddress resolve_hostname(const String &p_hostname, Type p_type = TYPE_ANY); + Array resolve_hostname_addresses(const String &p_hostname, Type p_type = TYPE_ANY); // async resolver hostname ResolverID resolve_hostname_queue_item(const String &p_hostname, Type p_type = TYPE_ANY); ResolverStatus get_resolve_item_status(ResolverID p_id) const; - IP_Address get_resolve_item_address(ResolverID p_id) const; - virtual void get_local_addresses(List<IP_Address> *r_addresses) const; + IPAddress get_resolve_item_address(ResolverID p_id) const; + virtual void get_local_addresses(List<IPAddress> *r_addresses) const; + + virtual void _resolve_hostname(List<IPAddress> &r_addresses, const String &p_hostname, Type p_type = TYPE_ANY) const = 0; + Array get_resolve_item_addresses(ResolverID p_id) const; + virtual void get_local_interfaces(Map<String, Interface_Info> *r_interfaces) const = 0; void erase_resolve_item(ResolverID p_id); diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp index d0fb63b958..1c1ac8a88f 100644 --- a/core/io/ip_address.cpp +++ b/core/io/ip_address.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,15 +30,14 @@ #include "ip_address.h" /* -IP_Address::operator Variant() const { - +IPAddress::operator Variant() const { return operator String(); }*/ #include <stdio.h> #include <string.h> -IP_Address::operator String() const { +IPAddress::operator String() const { if (wildcard) { return "*"; } @@ -91,7 +90,7 @@ static void _parse_hex(const String &p_string, int p_start, uint8_t *p_dst) { p_dst[1] = ret & 0xff; } -void IP_Address::_parse_ipv6(const String &p_string) { +void IPAddress::_parse_ipv6(const String &p_string) { static const int parts_total = 8; int parts[parts_total] = { 0 }; int parts_count = 0; @@ -147,7 +146,7 @@ void IP_Address::_parse_ipv6(const String &p_string) { } } -void IP_Address::_parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret) { +void IPAddress::_parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret) { String ip; if (p_start != 0) { ip = p_string.substr(p_start, p_string.length() - p_start); @@ -162,33 +161,33 @@ void IP_Address::_parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret } } -void IP_Address::clear() { +void IPAddress::clear() { memset(&field8[0], 0, sizeof(field8)); valid = false; wildcard = false; } -bool IP_Address::is_ipv4() const { +bool IPAddress::is_ipv4() const { return (field32[0] == 0 && field32[1] == 0 && field16[4] == 0 && field16[5] == 0xffff); } -const uint8_t *IP_Address::get_ipv4() const { +const uint8_t *IPAddress::get_ipv4() const { ERR_FAIL_COND_V_MSG(!is_ipv4(), &(field8[12]), "IPv4 requested, but current IP is IPv6."); // Not the correct IPv4 (it's an IPv6), but we don't want to return a null pointer risking an engine crash. return &(field8[12]); } -void IP_Address::set_ipv4(const uint8_t *p_ip) { +void IPAddress::set_ipv4(const uint8_t *p_ip) { clear(); valid = true; field16[5] = 0xffff; field32[3] = *((const uint32_t *)p_ip); } -const uint8_t *IP_Address::get_ipv6() const { +const uint8_t *IPAddress::get_ipv6() const { return field8; } -void IP_Address::set_ipv6(const uint8_t *p_buf) { +void IPAddress::set_ipv6(const uint8_t *p_buf) { clear(); valid = true; for (int i = 0; i < 16; i++) { @@ -196,7 +195,7 @@ void IP_Address::set_ipv6(const uint8_t *p_buf) { } } -IP_Address::IP_Address(const String &p_string) { +IPAddress::IPAddress(const String &p_string) { clear(); if (p_string == "*") { @@ -226,7 +225,7 @@ _FORCE_INLINE_ static void _32_to_buf(uint8_t *p_dst, uint32_t p_n) { p_dst[3] = (p_n >> 0) & 0xff; } -IP_Address::IP_Address(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6) { +IPAddress::IPAddress(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6) { clear(); valid = true; if (!is_v6) { diff --git a/core/io/ip_address.h b/core/io/ip_address.h index 2f8f83503e..05da675704 100644 --- a/core/io/ip_address.h +++ b/core/io/ip_address.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,9 @@ #ifndef IP_ADDRESS_H #define IP_ADDRESS_H -#include "core/ustring.h" +#include "core/string/ustring.h" -struct IP_Address { +struct IPAddress { private: union { uint8_t field8[16]; @@ -50,7 +50,7 @@ protected: public: //operator Variant() const; - bool operator==(const IP_Address &p_ip) const { + bool operator==(const IPAddress &p_ip) const { if (p_ip.valid != valid) { return false; } @@ -65,7 +65,7 @@ public: return true; } - bool operator!=(const IP_Address &p_ip) const { + bool operator!=(const IPAddress &p_ip) const { if (p_ip.valid != valid) { return true; } @@ -91,9 +91,9 @@ public: void set_ipv6(const uint8_t *p_buf); operator String() const; - IP_Address(const String &p_string); - IP_Address(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6 = false); - IP_Address() { clear(); } + IPAddress(const String &p_string); + IPAddress(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, bool is_v6 = false); + IPAddress() { clear(); } }; #endif // IP_ADDRESS_H diff --git a/core/io/json.cpp b/core/io/json.cpp index 1b89d966fd..5823afbdcd 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,7 +30,7 @@ #include "json.h" -#include "core/print_string.h" +#include "core/string/print_string.h" const char *JSON::tk_name[TK_MAX] = { "'{'", @@ -45,9 +45,9 @@ const char *JSON::tk_name[TK_MAX] = { "EOF", }; -static String _make_indent(const String &p_indent, int p_size) { +String JSON::_make_indent(const String &p_indent, int p_size) { String indent_text = ""; - if (!p_indent.empty()) { + if (!p_indent.is_empty()) { for (int i = 0; i < p_size; i++) { indent_text += p_indent; } @@ -55,11 +55,11 @@ static String _make_indent(const String &p_indent, int p_size) { return indent_text; } -String JSON::_print_var(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys) { +String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, Set<const void *> &p_markers, bool p_full_precision) { String colon = ":"; String end_statement = ""; - if (!p_indent.empty()) { + if (!p_indent.is_empty()) { colon += " "; end_statement += "\n"; } @@ -71,8 +71,17 @@ String JSON::_print_var(const Variant &p_var, const String &p_indent, int p_cur_ return p_var.operator bool() ? "true" : "false"; case Variant::INT: return itos(p_var); - case Variant::FLOAT: - return rtos(p_var); + case Variant::FLOAT: { + double num = p_var; + if (p_full_precision) { + // Store unreliable digits (17) instead of just reliable + // digits (14) so that the value can be decoded exactly. + return String::num(num, 17 - (int)floor(log10(num))); + } else { + // Store only reliable digits (14) by default. + return String::num(num, 14 - (int)floor(log10(num))); + } + } case Variant::PACKED_INT32_ARRAY: case Variant::PACKED_INT64_ARRAY: case Variant::PACKED_FLOAT32_ARRAY: @@ -82,20 +91,29 @@ String JSON::_print_var(const Variant &p_var, const String &p_indent, int p_cur_ String s = "["; s += end_statement; Array a = p_var; + + ERR_FAIL_COND_V_MSG(p_markers.has(a.id()), "\"[...]\"", "Converting circular structure to JSON."); + p_markers.insert(a.id()); + for (int i = 0; i < a.size(); i++) { if (i > 0) { s += ","; s += end_statement; } - s += _make_indent(p_indent, p_cur_indent + 1) + _print_var(a[i], p_indent, p_cur_indent + 1, p_sort_keys); + s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(a[i], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "]"; + p_markers.erase(a.id()); return s; } case Variant::DICTIONARY: { String s = "{"; s += end_statement; Dictionary d = p_var; + + ERR_FAIL_COND_V_MSG(p_markers.has(d.id()), "\"{...}\"", "Converting circular structure to JSON."); + p_markers.insert(d.id()); + List<Variant> keys; d.get_key_list(&keys); @@ -103,17 +121,21 @@ String JSON::_print_var(const Variant &p_var, const String &p_indent, int p_cur_ keys.sort(); } - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - if (E != keys.front()) { + bool first_key = true; + for (const Variant &E : keys) { + if (first_key) { + first_key = false; + } else { s += ","; s += end_statement; } - s += _make_indent(p_indent, p_cur_indent + 1) + _print_var(String(E->get()), p_indent, p_cur_indent + 1, p_sort_keys); + s += _make_indent(p_indent, p_cur_indent + 1) + _stringify(String(E), p_indent, p_cur_indent + 1, p_sort_keys, p_markers); s += colon; - s += _print_var(d[E->get()], p_indent, p_cur_indent + 1, p_sort_keys); + s += _stringify(d[E], p_indent, p_cur_indent + 1, p_sort_keys, p_markers); } s += end_statement + _make_indent(p_indent, p_cur_indent) + "}"; + p_markers.erase(d.id()); return s; } default: @@ -121,10 +143,6 @@ String JSON::_print_var(const Variant &p_var, const String &p_indent, int p_cur_ } } -String JSON::print(const Variant &p_var, const String &p_indent, bool p_sort_keys) { - return _print_var(p_var, p_indent, 0, p_sort_keys); -} - Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str) { while (p_len > 0) { switch (p_str[index]) { @@ -234,6 +252,52 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to } index += 4; //will add at the end anyway + if ((res & 0xfffffc00) == 0xd800) { + if (p_str[index + 1] != '\\' || p_str[index + 2] != 'u') { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + return ERR_PARSE_ERROR; + } + index += 2; + char32_t trail = 0; + for (int j = 0; j < 4; j++) { + char32_t c = p_str[index + j + 1]; + if (c == 0) { + r_err_str = "Unterminated String"; + return ERR_PARSE_ERROR; + } + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + r_err_str = "Malformed hex constant in string"; + return ERR_PARSE_ERROR; + } + char32_t v; + if (c >= '0' && c <= '9') { + v = c - '0'; + } else if (c >= 'a' && c <= 'f') { + v = c - 'a'; + v += 10; + } else if (c >= 'A' && c <= 'F') { + v = c - 'A'; + v += 10; + } else { + ERR_PRINT("Bug parsing hex constant."); + v = 0; + } + + trail <<= 4; + trail |= v; + } + if ((trail & 0xfffffc00) == 0xdc00) { + res = (res << 10UL) + trail - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + index += 4; //will add at the end anyway + } else { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + r_err_str = "Invalid UTF-16 sequence in string, unpaired trail surrogate"; + return ERR_PARSE_ERROR; + } + } break; default: { res = next; @@ -301,7 +365,6 @@ Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, in return err; } value = d; - return OK; } else if (token.type == TK_BRACKET_OPEN) { Array a; Error err = _parse_array(a, p_str, index, p_len, line, r_err_str); @@ -309,8 +372,6 @@ Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, in return err; } value = a; - return OK; - } else if (token.type == TK_IDENTIFIER) { String id = token.value; if (id == "true") { @@ -323,18 +384,16 @@ Error JSON::_parse_value(Variant &value, Token &token, const char32_t *p_str, in r_err_str = "Expected 'true','false' or 'null', got '" + id + "'."; return ERR_PARSE_ERROR; } - return OK; - } else if (token.type == TK_NUMBER) { value = token.value; - return OK; } else if (token.type == TK_STRING) { value = token.value; - return OK; } else { r_err_str = "Expected value, got " + String(tk_name[token.type]) + "."; return ERR_PARSE_ERROR; } + + return OK; } Error JSON::_parse_array(Array &array, const char32_t *p_str, int &index, int p_len, int &line, String &r_err_str) { @@ -438,7 +497,7 @@ Error JSON::_parse_object(Dictionary &object, const char32_t *p_str, int &index, return ERR_PARSE_ERROR; } -Error JSON::parse(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line) { +Error JSON::_parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line) { const char32_t *str = p_json.ptr(); int idx = 0; int len = p_json.length(); @@ -453,5 +512,40 @@ Error JSON::parse(const String &p_json, Variant &r_ret, String &r_err_str, int & err = _parse_value(r_ret, token, str, idx, len, r_err_line, r_err_str); + // Check if EOF is reached + // or it's a type of the next token. + if (err == OK && idx < len) { + err = _get_token(str, idx, len, token, r_err_line, r_err_str); + + if (err || token.type != TK_EOF) { + r_err_str = "Expected 'EOF'"; + // Reset return value to empty `Variant` + r_ret = Variant(); + return ERR_PARSE_ERROR; + } + } + return err; } + +String JSON::stringify(const Variant &p_var, const String &p_indent, bool p_sort_keys, bool p_full_precision) { + Set<const void *> markers; + return _stringify(p_var, p_indent, 0, p_sort_keys, markers, p_full_precision); +} + +Error JSON::parse(const String &p_json_string) { + Error err = _parse_string(p_json_string, data, err_str, err_line); + if (err == Error::OK) { + err_line = 0; + } + return err; +} + +void JSON::_bind_methods() { + ClassDB::bind_method(D_METHOD("stringify", "data", "indent", "sort_keys", "full_precision"), &JSON::stringify, DEFVAL(""), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("parse", "json_string"), &JSON::parse); + + ClassDB::bind_method(D_METHOD("get_data"), &JSON::get_data); + ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); + ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message); +} diff --git a/core/io/json.h b/core/io/json.h index 9122228163..f20c97f540 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,12 @@ #ifndef JSON_H #define JSON_H -#include "core/variant.h" +#include "core/object/ref_counted.h" +#include "core/variant/variant.h" + +class JSON : public RefCounted { + GDCLASS(JSON, RefCounted); -class JSON { enum TokenType { TK_CURLY_BRACKET_OPEN, TK_CURLY_BRACKET_CLOSE, @@ -49,7 +52,6 @@ class JSON { }; enum Expecting { - EXPECT_OBJECT, EXPECT_OBJECT_KEY, EXPECT_COLON, @@ -61,18 +63,30 @@ class JSON { Variant value; }; - static const char *tk_name[TK_MAX]; + Variant data; + String err_str; + int err_line = 0; - static String _print_var(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys); + static const char *tk_name[]; + static String _make_indent(const String &p_indent, int p_size); + static String _stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, Set<const void *> &p_markers, bool p_full_precision = false); static Error _get_token(const char32_t *p_str, int &index, int p_len, Token &r_token, int &line, String &r_err_str); static Error _parse_value(Variant &value, Token &token, const char32_t *p_str, int &index, int p_len, int &line, String &r_err_str); static Error _parse_array(Array &array, const char32_t *p_str, int &index, int p_len, int &line, String &r_err_str); static Error _parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, String &r_err_str); + static Error _parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line); + +protected: + static void _bind_methods(); public: - static String print(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true); - static Error parse(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line); + String stringify(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true, bool p_full_precision = false); + Error parse(const String &p_json_string); + + inline Variant get_data() const { return data; } + inline int get_error_line() const { return err_line; } + inline String get_error_message() const { return err_str; } }; #endif // JSON_H diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 886e5695b1..09539f716c 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,11 @@ #include "logger.h" -#include "core/os/dir_access.h" +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" #include "core/os/os.h" -#include "core/print_string.h" +#include "core/os/time.h" +#include "core/string/print_string.h" #if defined(MINGW_ENABLED) || defined(_MSC_VER) #define sprintf sprintf_s @@ -42,6 +44,12 @@ bool Logger::should_log(bool p_err) { return (!p_err || _print_error_enabled) && (p_err || _print_line_enabled); } +bool Logger::_flush_stdout_on_print = true; + +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) { if (!should_log(true)) { return; @@ -149,11 +157,7 @@ void RotatedFileLogger::rotate_file() { if (FileAccess::exists(base_path)) { if (max_files > 1) { - char timestamp[21]; - OS::Date date = OS::get_singleton()->get_date(); - OS::Time time = OS::get_singleton()->get_time(); - sprintf(timestamp, "_%04d-%02d-%02d_%02d-%02d-%02d", date.year, date.month, date.day, time.hour, time.min, time.sec); - + String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace(":", "."); String backup_name = base_path.get_basename() + timestamp; if (base_path.get_extension() != String()) { backup_name += "." + base_path.get_extension(); @@ -201,15 +205,14 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) { } va_end(list_copy); file->store_buffer((uint8_t *)buf, len); + if (len >= static_buf_size) { Memory::free_static(buf); } -#ifdef DEBUG_ENABLED - const bool need_flush = true; -#else - bool need_flush = p_err; -#endif - if (need_flush) { + + if (p_err || _flush_stdout_on_print) { + // Don't always flush when printing stdout to avoid performance + // issues when `print()` is spammed in release builds. file->flush(); } } @@ -228,9 +231,11 @@ void StdLogger::logv(const char *p_format, va_list p_list, bool p_err) { vfprintf(stderr, p_format, p_list); } else { vprintf(p_format, p_list); -#ifdef DEBUG_ENABLED - fflush(stdout); -#endif + if (_flush_stdout_on_print) { + // Don't always flush when printing stdout to avoid performance + // issues when `print()` is spammed in release builds. + fflush(stdout); + } } } diff --git a/core/io/logger.h b/core/io/logger.h index 277be9ed35..ccf68562d6 100644 --- a/core/io/logger.h +++ b/core/io/logger.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,9 @@ #ifndef LOGGER_H #define LOGGER_H -#include "core/os/file_access.h" -#include "core/ustring.h" -#include "core/vector.h" +#include "core/io/file_access.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" #include <stdarg.h> @@ -41,6 +41,8 @@ class Logger { protected: bool should_log(bool p_err); + static bool _flush_stdout_on_print; + public: enum ErrorType { ERR_ERROR, @@ -49,6 +51,8 @@ public: ERR_SHADER }; + 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); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index eb39b1433f..e7d5b78d14 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,9 @@ #include "marshalls.h" +#include "core/object/ref_counted.h" #include "core/os/keyboard.h" -#include "core/print_string.h" -#include "core/reference.h" +#include "core/string/print_string.h" #include <limits.h> #include <stdio.h> @@ -111,6 +111,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len = 4; } + // Note: We cannot use sizeof(real_t) for decoding, in case a different size is encoded. + // Decoding math types always checks for the encoded size, while encoding always uses compilation setting. + // This does lead to some code duplication for decoding, but compatibility is the priority. switch (type & ENCODE_MASK) { case Variant::NIL: { r_variant = Variant(); @@ -144,18 +147,18 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::FLOAT: { if (type & ENCODE_FLAG_64) { - ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA); + ERR_FAIL_COND_V((size_t)len < sizeof(double), ERR_INVALID_DATA); double val = decode_double(buf); r_variant = val; if (r_len) { - (*r_len) += 8; + (*r_len) += sizeof(double); } } else { - ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); + ERR_FAIL_COND_V((size_t)len < sizeof(float), ERR_INVALID_DATA); float val = decode_float(buf); r_variant = val; if (r_len) { - (*r_len) += 4; + (*r_len) += sizeof(float); } } @@ -172,15 +175,25 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int // math types case Variant::VECTOR2: { - ERR_FAIL_COND_V(len < 4 * 2, ERR_INVALID_DATA); Vector2 val; - val.x = decode_float(&buf[0]); - val.y = decode_float(&buf[4]); - r_variant = val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 2, ERR_INVALID_DATA); + val.x = decode_double(&buf[0]); + val.y = decode_double(&buf[sizeof(double)]); - if (r_len) { - (*r_len) += 4 * 2; + if (r_len) { + (*r_len) += sizeof(double) * 2; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 2, ERR_INVALID_DATA); + val.x = decode_float(&buf[0]); + val.y = decode_float(&buf[sizeof(float)]); + + if (r_len) { + (*r_len) += sizeof(float) * 2; + } } + r_variant = val; } break; case Variant::VECTOR2I: { @@ -196,17 +209,29 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::RECT2: { - ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA); Rect2 val; - val.position.x = decode_float(&buf[0]); - val.position.y = decode_float(&buf[4]); - val.size.x = decode_float(&buf[8]); - val.size.y = decode_float(&buf[12]); - r_variant = val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA); + val.position.x = decode_double(&buf[0]); + val.position.y = decode_double(&buf[sizeof(double)]); + val.size.x = decode_double(&buf[sizeof(double) * 2]); + val.size.y = decode_double(&buf[sizeof(double) * 3]); - if (r_len) { - (*r_len) += 4 * 4; + if (r_len) { + (*r_len) += sizeof(double) * 4; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 4, ERR_INVALID_DATA); + val.position.x = decode_float(&buf[0]); + val.position.y = decode_float(&buf[sizeof(float)]); + val.size.x = decode_float(&buf[sizeof(float) * 2]); + val.size.y = decode_float(&buf[sizeof(float) * 3]); + + if (r_len) { + (*r_len) += sizeof(float) * 4; + } } + r_variant = val; } break; case Variant::RECT2I: { @@ -224,16 +249,27 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::VECTOR3: { - ERR_FAIL_COND_V(len < 4 * 3, ERR_INVALID_DATA); Vector3 val; - val.x = decode_float(&buf[0]); - val.y = decode_float(&buf[4]); - val.z = decode_float(&buf[8]); - r_variant = val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 3, ERR_INVALID_DATA); + val.x = decode_double(&buf[0]); + val.y = decode_double(&buf[sizeof(double)]); + val.z = decode_double(&buf[sizeof(double) * 2]); - if (r_len) { - (*r_len) += 4 * 3; + if (r_len) { + (*r_len) += sizeof(double) * 3; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 3, ERR_INVALID_DATA); + val.x = decode_float(&buf[0]); + val.y = decode_float(&buf[sizeof(float)]); + val.z = decode_float(&buf[sizeof(float) * 2]); + + if (r_len) { + (*r_len) += sizeof(float) * 3; + } } + r_variant = val; } break; case Variant::VECTOR3I: { @@ -250,101 +286,177 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::TRANSFORM2D: { - ERR_FAIL_COND_V(len < 4 * 6, ERR_INVALID_DATA); Transform2D val; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 2; j++) { - val.elements[i][j] = decode_float(&buf[(i * 2 + j) * 4]); + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 6, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 2; j++) { + val.elements[i][j] = decode_double(&buf[(i * 2 + j) * sizeof(double)]); + } } - } - r_variant = val; + if (r_len) { + (*r_len) += sizeof(double) * 6; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 6, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 2; j++) { + val.elements[i][j] = decode_float(&buf[(i * 2 + j) * sizeof(float)]); + } + } - if (r_len) { - (*r_len) += 4 * 6; + if (r_len) { + (*r_len) += sizeof(float) * 6; + } } + r_variant = val; } break; case Variant::PLANE: { - ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA); Plane val; - val.normal.x = decode_float(&buf[0]); - val.normal.y = decode_float(&buf[4]); - val.normal.z = decode_float(&buf[8]); - val.d = decode_float(&buf[12]); - r_variant = val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA); + val.normal.x = decode_double(&buf[0]); + val.normal.y = decode_double(&buf[sizeof(double)]); + val.normal.z = decode_double(&buf[sizeof(double) * 2]); + val.d = decode_double(&buf[sizeof(double) * 3]); - if (r_len) { - (*r_len) += 4 * 4; + if (r_len) { + (*r_len) += sizeof(double) * 4; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 4, ERR_INVALID_DATA); + val.normal.x = decode_float(&buf[0]); + val.normal.y = decode_float(&buf[sizeof(float)]); + val.normal.z = decode_float(&buf[sizeof(float) * 2]); + val.d = decode_float(&buf[sizeof(float) * 3]); + + if (r_len) { + (*r_len) += sizeof(float) * 4; + } } + r_variant = val; } break; - case Variant::QUAT: { - ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA); - Quat val; - val.x = decode_float(&buf[0]); - val.y = decode_float(&buf[4]); - val.z = decode_float(&buf[8]); - val.w = decode_float(&buf[12]); - r_variant = val; + case Variant::QUATERNION: { + Quaternion val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 4, ERR_INVALID_DATA); + val.x = decode_double(&buf[0]); + val.y = decode_double(&buf[sizeof(double)]); + val.z = decode_double(&buf[sizeof(double) * 2]); + val.w = decode_double(&buf[sizeof(double) * 3]); - if (r_len) { - (*r_len) += 4 * 4; + if (r_len) { + (*r_len) += sizeof(double) * 4; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 4, ERR_INVALID_DATA); + val.x = decode_float(&buf[0]); + val.y = decode_float(&buf[sizeof(float)]); + val.z = decode_float(&buf[sizeof(float) * 2]); + val.w = decode_float(&buf[sizeof(float) * 3]); + + if (r_len) { + (*r_len) += sizeof(float) * 4; + } } + r_variant = val; } break; case Variant::AABB: { - ERR_FAIL_COND_V(len < 4 * 6, ERR_INVALID_DATA); AABB val; - val.position.x = decode_float(&buf[0]); - val.position.y = decode_float(&buf[4]); - val.position.z = decode_float(&buf[8]); - val.size.x = decode_float(&buf[12]); - val.size.y = decode_float(&buf[16]); - val.size.z = decode_float(&buf[20]); - r_variant = val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 6, ERR_INVALID_DATA); + val.position.x = decode_double(&buf[0]); + val.position.y = decode_double(&buf[sizeof(double)]); + val.position.z = decode_double(&buf[sizeof(double) * 2]); + val.size.x = decode_double(&buf[sizeof(double) * 3]); + val.size.y = decode_double(&buf[sizeof(double) * 4]); + val.size.z = decode_double(&buf[sizeof(double) * 5]); - if (r_len) { - (*r_len) += 4 * 6; + if (r_len) { + (*r_len) += sizeof(double) * 6; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 6, ERR_INVALID_DATA); + val.position.x = decode_float(&buf[0]); + val.position.y = decode_float(&buf[sizeof(float)]); + val.position.z = decode_float(&buf[sizeof(float) * 2]); + val.size.x = decode_float(&buf[sizeof(float) * 3]); + val.size.y = decode_float(&buf[sizeof(float) * 4]); + val.size.z = decode_float(&buf[sizeof(float) * 5]); + + if (r_len) { + (*r_len) += sizeof(float) * 6; + } } + r_variant = val; } break; case Variant::BASIS: { - ERR_FAIL_COND_V(len < 4 * 9, ERR_INVALID_DATA); Basis val; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - val.elements[i][j] = decode_float(&buf[(i * 3 + j) * 4]); + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 9, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + val.elements[i][j] = decode_double(&buf[(i * 3 + j) * sizeof(double)]); + } } - } - r_variant = val; + if (r_len) { + (*r_len) += sizeof(double) * 9; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 9, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + val.elements[i][j] = decode_float(&buf[(i * 3 + j) * sizeof(float)]); + } + } - if (r_len) { - (*r_len) += 4 * 9; + if (r_len) { + (*r_len) += sizeof(float) * 9; + } } + r_variant = val; } break; - case Variant::TRANSFORM: { - ERR_FAIL_COND_V(len < 4 * 12, ERR_INVALID_DATA); - Transform val; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - val.basis.elements[i][j] = decode_float(&buf[(i * 3 + j) * 4]); + case Variant::TRANSFORM3D: { + Transform3D val; + if (type & ENCODE_FLAG_64) { + ERR_FAIL_COND_V((size_t)len < sizeof(double) * 12, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + val.basis.elements[i][j] = decode_double(&buf[(i * 3 + j) * sizeof(double)]); + } } - } - val.origin[0] = decode_float(&buf[36]); - val.origin[1] = decode_float(&buf[40]); - val.origin[2] = decode_float(&buf[44]); + val.origin[0] = decode_double(&buf[sizeof(double) * 9]); + val.origin[1] = decode_double(&buf[sizeof(double) * 10]); + val.origin[2] = decode_double(&buf[sizeof(double) * 11]); - r_variant = val; + if (r_len) { + (*r_len) += sizeof(double) * 12; + } + } else { + ERR_FAIL_COND_V((size_t)len < sizeof(float) * 12, ERR_INVALID_DATA); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + val.basis.elements[i][j] = decode_float(&buf[(i * 3 + j) * sizeof(float)]); + } + } + val.origin[0] = decode_float(&buf[sizeof(float) * 9]); + val.origin[1] = decode_float(&buf[sizeof(float) * 10]); + val.origin[2] = decode_float(&buf[sizeof(float) * 11]); - if (r_len) { - (*r_len) += 4 * 12; + if (r_len) { + (*r_len) += sizeof(float) * 12; + } } + r_variant = val; } break; - // misc types case Variant::COLOR: { ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA); @@ -356,9 +468,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int r_variant = val; if (r_len) { - (*r_len) += 4 * 4; + (*r_len) += 4 * 4; // Colors should always be in single-precision. } - } break; case Variant::STRING_NAME: { String str; @@ -420,7 +531,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } } break; - case Variant::_RID: { + case Variant::RID: { r_variant = RID(); } break; case Variant::OBJECT: { @@ -436,7 +547,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int r_variant = (Object *)nullptr; } else { Ref<EncodedObjectAsID> obj_as_id; - obj_as_id.instance(); + obj_as_id.instantiate(); obj_as_id->set_object_id(val); r_variant = obj_as_id; @@ -454,7 +565,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int if (str == String()) { r_variant = (Object *)nullptr; } else { - Object *obj = ClassDB::instance(str); + Object *obj = ClassDB::instantiate(str); ERR_FAIL_COND_V(!obj, ERR_UNAVAILABLE); ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); @@ -463,7 +574,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int buf += 4; len -= 4; if (r_len) { - (*r_len) += 4; + (*r_len) += 4; // Size of count number. } for (int i = 0; i < count; i++) { @@ -489,8 +600,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int obj->set(str, value); } - if (Object::cast_to<Reference>(obj)) { - REF ref = REF(Object::cast_to<Reference>(obj)); + if (Object::cast_to<RefCounted>(obj)) { + REF ref = REF(Object::cast_to<RefCounted>(obj)); r_variant = ref; } else { r_variant = obj; @@ -516,7 +627,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int len -= 4; if (r_len) { - (*r_len) += 4; + (*r_len) += 4; // Size of count number. } Dictionary d; @@ -559,7 +670,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int len -= 4; if (r_len) { - (*r_len) += 4; + (*r_len) += 4; // Size of count number. } Array varr; @@ -635,7 +746,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_INT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -684,7 +795,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int } break; case Variant::PACKED_FLOAT64_ARRAY: { ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA); - int64_t count = decode_uint64(buf); + int32_t count = decode_uint32(buf); buf += 4; len -= 4; ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA); @@ -693,7 +804,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int Vector<double> data; if (count) { - //const double*rbuf=(const double*)buf; data.resize(count); double *w = data.ptrw(); for (int64_t i = 0; i < count; i++) { @@ -716,9 +826,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int len -= 4; if (r_len) { - (*r_len) += 4; + (*r_len) += 4; // Size of count number. } - //printf("string count: %i\n",count); for (int32_t i = 0; i < count; i++) { String str; @@ -739,30 +848,57 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int buf += 4; len -= 4; - ERR_FAIL_MUL_OF(count, 4 * 2, ERR_INVALID_DATA); - ERR_FAIL_COND_V(count < 0 || count * 4 * 2 > len, ERR_INVALID_DATA); Vector<Vector2> varray; - if (r_len) { - (*r_len) += 4; - } - - if (count) { - varray.resize(count); - Vector2 *w = varray.ptrw(); + if (type & ENCODE_FLAG_64) { + ERR_FAIL_MUL_OF(count, sizeof(double) * 2, ERR_INVALID_DATA); + ERR_FAIL_COND_V(count < 0 || count * sizeof(double) * 2 > (size_t)len, ERR_INVALID_DATA); - for (int32_t i = 0; i < count; i++) { - w[i].x = decode_float(buf + i * 4 * 2 + 4 * 0); - w[i].y = decode_float(buf + i * 4 * 2 + 4 * 1); + if (r_len) { + (*r_len) += 4; // Size of count number. } - int adv = 4 * 2 * count; + if (count) { + varray.resize(count); + Vector2 *w = varray.ptrw(); + + for (int32_t i = 0; i < count; i++) { + w[i].x = decode_double(buf + i * sizeof(double) * 2 + sizeof(double) * 0); + w[i].y = decode_double(buf + i * sizeof(double) * 2 + sizeof(double) * 1); + } + + int adv = sizeof(double) * 2 * count; + + if (r_len) { + (*r_len) += adv; + } + len -= adv; + buf += adv; + } + } else { + ERR_FAIL_MUL_OF(count, sizeof(float) * 2, ERR_INVALID_DATA); + ERR_FAIL_COND_V(count < 0 || count * sizeof(float) * 2 > (size_t)len, ERR_INVALID_DATA); if (r_len) { - (*r_len) += adv; + (*r_len) += 4; // Size of count number. } - } + if (count) { + varray.resize(count); + Vector2 *w = varray.ptrw(); + + for (int32_t i = 0; i < count; i++) { + w[i].x = decode_float(buf + i * sizeof(float) * 2 + sizeof(float) * 0); + w[i].y = decode_float(buf + i * sizeof(float) * 2 + sizeof(float) * 1); + } + + int adv = sizeof(float) * 2 * count; + + if (r_len) { + (*r_len) += adv; + } + } + } r_variant = varray; } break; @@ -772,32 +908,61 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int buf += 4; len -= 4; - ERR_FAIL_MUL_OF(count, 4 * 3, ERR_INVALID_DATA); - ERR_FAIL_COND_V(count < 0 || count * 4 * 3 > len, ERR_INVALID_DATA); - Vector<Vector3> varray; - if (r_len) { - (*r_len) += 4; - } - - if (count) { - varray.resize(count); - Vector3 *w = varray.ptrw(); + if (type & ENCODE_FLAG_64) { + ERR_FAIL_MUL_OF(count, sizeof(double) * 3, ERR_INVALID_DATA); + ERR_FAIL_COND_V(count < 0 || count * sizeof(double) * 3 > (size_t)len, ERR_INVALID_DATA); - for (int32_t i = 0; i < count; i++) { - w[i].x = decode_float(buf + i * 4 * 3 + 4 * 0); - w[i].y = decode_float(buf + i * 4 * 3 + 4 * 1); - w[i].z = decode_float(buf + i * 4 * 3 + 4 * 2); + if (r_len) { + (*r_len) += 4; // Size of count number. } - int adv = 4 * 3 * count; + if (count) { + varray.resize(count); + Vector3 *w = varray.ptrw(); + + for (int32_t i = 0; i < count; i++) { + w[i].x = decode_double(buf + i * sizeof(double) * 3 + sizeof(double) * 0); + w[i].y = decode_double(buf + i * sizeof(double) * 3 + sizeof(double) * 1); + w[i].z = decode_double(buf + i * sizeof(double) * 3 + sizeof(double) * 2); + } + + int adv = sizeof(double) * 3 * count; + + if (r_len) { + (*r_len) += adv; + } + len -= adv; + buf += adv; + } + } else { + ERR_FAIL_MUL_OF(count, sizeof(float) * 3, ERR_INVALID_DATA); + ERR_FAIL_COND_V(count < 0 || count * sizeof(float) * 3 > (size_t)len, ERR_INVALID_DATA); if (r_len) { - (*r_len) += adv; + (*r_len) += 4; // Size of count number. } - } + if (count) { + varray.resize(count); + Vector3 *w = varray.ptrw(); + + for (int32_t i = 0; i < count; i++) { + w[i].x = decode_float(buf + i * sizeof(float) * 3 + sizeof(float) * 0); + w[i].y = decode_float(buf + i * sizeof(float) * 3 + sizeof(float) * 1); + w[i].z = decode_float(buf + i * sizeof(float) * 3 + sizeof(float) * 2); + } + + int adv = sizeof(float) * 3 * count; + + if (r_len) { + (*r_len) += adv; + } + len -= adv; + buf += adv; + } + } r_variant = varray; } break; @@ -813,7 +978,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int Vector<Color> carray; if (r_len) { - (*r_len) += 4; + (*r_len) += 4; // Size of count number. } if (count) { @@ -821,6 +986,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int Color *w = carray.ptrw(); for (int32_t i = 0; i < count; i++) { + // Colors should always be in single-precision. w[i].r = decode_float(buf + i * 4 * 4 + 4 * 0); w[i].g = decode_float(buf + i * 4 * 4 + 4 * 1); w[i].b = decode_float(buf + i * 4 * 4 + 4 * 2); @@ -851,7 +1017,7 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) { if (buf) { encode_uint32(utf8.length(), buf); buf += 4; - copymem(buf, utf8.get_data(), utf8.length()); + memcpy(buf, utf8.get_data(), utf8.length()); buf += utf8.length(); } @@ -864,7 +1030,8 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) { } } -Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects) { +Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects, int p_depth) { + ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Potential inifite recursion detected. Bailing."); uint8_t *buf = r_buffer; r_len = 0; @@ -882,14 +1049,14 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo double d = p_variant; float f = d; if (double(f) != d) { - flags |= ENCODE_FLAG_64; //always encode real as double + flags |= ENCODE_FLAG_64; } } break; case Variant::OBJECT: { // Test for potential wrong values sent by the debugger when it breaks. Object *obj = p_variant.get_validated_object(); if (!obj) { - // Object is invalid, send a nullptr instead. + // Object is invalid, send a nullptr instead. if (buf) { encode_uint32(Variant::NIL, buf); } @@ -995,7 +1162,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (buf) { encode_uint32(utf8.length(), buf); buf += 4; - copymem(buf, utf8.get_data(), utf8.length()); + memcpy(buf, utf8.get_data(), utf8.length()); buf += pad + utf8.length(); } @@ -1003,10 +1170,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo } } break; - case Variant::STRING: { - _encode_string(p_variant, buf, r_len); - - } break; + case Variant::STRING: case Variant::STRING_NAME: { _encode_string(p_variant, buf, r_len); @@ -1016,11 +1180,11 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo case Variant::VECTOR2: { if (buf) { Vector2 v2 = p_variant; - encode_float(v2.x, &buf[0]); - encode_float(v2.y, &buf[4]); + encode_real(v2.x, &buf[0]); + encode_real(v2.y, &buf[sizeof(real_t)]); } - r_len += 2 * 4; + r_len += 2 * sizeof(real_t); } break; case Variant::VECTOR2I: { @@ -1036,12 +1200,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo case Variant::RECT2: { if (buf) { Rect2 r2 = p_variant; - encode_float(r2.position.x, &buf[0]); - encode_float(r2.position.y, &buf[4]); - encode_float(r2.size.x, &buf[8]); - encode_float(r2.size.y, &buf[12]); + encode_real(r2.position.x, &buf[0]); + encode_real(r2.position.y, &buf[sizeof(real_t)]); + encode_real(r2.size.x, &buf[sizeof(real_t) * 2]); + encode_real(r2.size.y, &buf[sizeof(real_t) * 3]); } - r_len += 4 * 4; + r_len += 4 * sizeof(real_t); } break; case Variant::RECT2I: { @@ -1058,12 +1222,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo case Variant::VECTOR3: { if (buf) { Vector3 v3 = p_variant; - encode_float(v3.x, &buf[0]); - encode_float(v3.y, &buf[4]); - encode_float(v3.z, &buf[8]); + encode_real(v3.x, &buf[0]); + encode_real(v3.y, &buf[sizeof(real_t)]); + encode_real(v3.z, &buf[sizeof(real_t) * 2]); } - r_len += 3 * 4; + r_len += 3 * sizeof(real_t); } break; case Variant::VECTOR3I: { @@ -1082,50 +1246,50 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Transform2D val = p_variant; for (int i = 0; i < 3; i++) { for (int j = 0; j < 2; j++) { - copymem(&buf[(i * 2 + j) * 4], &val.elements[i][j], sizeof(float)); + memcpy(&buf[(i * 2 + j) * sizeof(real_t)], &val.elements[i][j], sizeof(real_t)); } } } - r_len += 6 * 4; + r_len += 6 * sizeof(real_t); } break; case Variant::PLANE: { if (buf) { Plane p = p_variant; - encode_float(p.normal.x, &buf[0]); - encode_float(p.normal.y, &buf[4]); - encode_float(p.normal.z, &buf[8]); - encode_float(p.d, &buf[12]); + encode_real(p.normal.x, &buf[0]); + encode_real(p.normal.y, &buf[sizeof(real_t)]); + encode_real(p.normal.z, &buf[sizeof(real_t) * 2]); + encode_real(p.d, &buf[sizeof(real_t) * 3]); } - r_len += 4 * 4; + r_len += 4 * sizeof(real_t); } break; - case Variant::QUAT: { + case Variant::QUATERNION: { if (buf) { - Quat q = p_variant; - encode_float(q.x, &buf[0]); - encode_float(q.y, &buf[4]); - encode_float(q.z, &buf[8]); - encode_float(q.w, &buf[12]); + Quaternion q = p_variant; + encode_real(q.x, &buf[0]); + encode_real(q.y, &buf[sizeof(real_t)]); + encode_real(q.z, &buf[sizeof(real_t) * 2]); + encode_real(q.w, &buf[sizeof(real_t) * 3]); } - r_len += 4 * 4; + r_len += 4 * sizeof(real_t); } break; case Variant::AABB: { if (buf) { AABB aabb = p_variant; - encode_float(aabb.position.x, &buf[0]); - encode_float(aabb.position.y, &buf[4]); - encode_float(aabb.position.z, &buf[8]); - encode_float(aabb.size.x, &buf[12]); - encode_float(aabb.size.y, &buf[16]); - encode_float(aabb.size.z, &buf[20]); + encode_real(aabb.position.x, &buf[0]); + encode_real(aabb.position.y, &buf[sizeof(real_t)]); + encode_real(aabb.position.z, &buf[sizeof(real_t) * 2]); + encode_real(aabb.size.x, &buf[sizeof(real_t) * 3]); + encode_real(aabb.size.y, &buf[sizeof(real_t) * 4]); + encode_real(aabb.size.z, &buf[sizeof(real_t) * 5]); } - r_len += 6 * 4; + r_len += 6 * sizeof(real_t); } break; case Variant::BASIS: { @@ -1133,29 +1297,29 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Basis val = p_variant; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - copymem(&buf[(i * 3 + j) * 4], &val.elements[i][j], sizeof(float)); + memcpy(&buf[(i * 3 + j) * sizeof(real_t)], &val.elements[i][j], sizeof(real_t)); } } } - r_len += 9 * 4; + r_len += 9 * sizeof(real_t); } break; - case Variant::TRANSFORM: { + case Variant::TRANSFORM3D: { if (buf) { - Transform val = p_variant; + Transform3D val = p_variant; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - copymem(&buf[(i * 3 + j) * 4], &val.basis.elements[i][j], sizeof(float)); + memcpy(&buf[(i * 3 + j) * sizeof(real_t)], &val.basis.elements[i][j], sizeof(real_t)); } } - encode_float(val.origin.x, &buf[36]); - encode_float(val.origin.y, &buf[40]); - encode_float(val.origin.z, &buf[44]); + encode_real(val.origin.x, &buf[sizeof(real_t) * 9]); + encode_real(val.origin.y, &buf[sizeof(real_t) * 10]); + encode_real(val.origin.z, &buf[sizeof(real_t) * 11]); } - r_len += 12 * 4; + r_len += 12 * sizeof(real_t); } break; @@ -1169,10 +1333,10 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo encode_float(c.a, &buf[12]); } - r_len += 4 * 4; + r_len += 4 * 4; // Colors should always be in single-precision. } break; - case Variant::_RID: { + case Variant::RID: { } break; case Variant::CALLABLE: { } break; @@ -1194,8 +1358,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo obj->get_property_list(&props); int pc = 0; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } pc++; @@ -1208,18 +1372,16 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo r_len += 4; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - _encode_string(E->get().name, buf, r_len); + _encode_string(E.name, buf, r_len); int len; - Error err = encode_variant(obj->get(E->get().name), buf, len, p_full_objects); - if (err) { - return err; - } + Error err = encode_variant(obj->get(E.name), buf, len, p_full_objects, p_depth + 1); + ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); r_len += len; if (buf) { @@ -1254,14 +1416,14 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo List<Variant> keys; d.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + for (const Variant &E : keys) { /* CharString utf8 = E->->utf8(); if (buf) { encode_uint32(utf8.length()+1,buf); buf+=4; - copymem(buf,utf8.get_data(),utf8.length()+1); + memcpy(buf,utf8.get_data(),utf8.length()+1); } r_len+=4+utf8.length()+1; @@ -1269,15 +1431,17 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo r_len++; //pad */ int len; - encode_variant(E->get(), buf, len, p_full_objects); + Error err = encode_variant(E, buf, len, p_full_objects, p_depth + 1); + ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); r_len += len; if (buf) { buf += len; } - Variant *v = d.getptr(E->get()); + Variant *v = d.getptr(E); ERR_FAIL_COND_V(!v, ERR_BUG); - encode_variant(*v, buf, len, p_full_objects); + err = encode_variant(*v, buf, len, p_full_objects, p_depth + 1); + ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); r_len += len; if (buf) { @@ -1298,7 +1462,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo for (int i = 0; i < v.size(); i++) { int len; - encode_variant(v.get(i), buf, len, p_full_objects); + Error err = encode_variant(v.get(i), buf, len, p_full_objects, p_depth + 1); + ERR_FAIL_COND_V(err, err); ERR_FAIL_COND_V(len % 4, ERR_BUG); r_len += len; if (buf) { @@ -1317,7 +1482,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo encode_uint32(datalen, buf); buf += 4; const uint8_t *r = data.ptr(); - copymem(buf, &r[0], datalen * datasize); + memcpy(buf, &r[0], datalen * datasize); buf += datalen * datasize; } @@ -1353,7 +1518,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo int datasize = sizeof(int64_t); if (buf) { - encode_uint64(datalen, buf); + encode_uint32(datalen, buf); buf += 4; const int64_t *r = data.ptr(); for (int64_t i = 0; i < datalen; i++) { @@ -1415,7 +1580,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (buf) { encode_uint32(utf8.length() + 1, buf); buf += 4; - copymem(buf, utf8.get_data(), utf8.length() + 1); + memcpy(buf, utf8.get_data(), utf8.length() + 1); buf += utf8.length() + 1; } @@ -1444,13 +1609,13 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo for (int i = 0; i < len; i++) { Vector2 v = data.get(i); - encode_float(v.x, &buf[0]); - encode_float(v.y, &buf[4]); - buf += 4 * 2; + encode_real(v.x, &buf[0]); + encode_real(v.y, &buf[sizeof(real_t)]); + buf += sizeof(real_t) * 2; } } - r_len += 4 * 2 * len; + r_len += sizeof(real_t) * 2 * len; } break; case Variant::PACKED_VECTOR3_ARRAY: { @@ -1468,14 +1633,14 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo for (int i = 0; i < len; i++) { Vector3 v = data.get(i); - encode_float(v.x, &buf[0]); - encode_float(v.y, &buf[4]); - encode_float(v.z, &buf[8]); - buf += 4 * 3; + encode_real(v.x, &buf[0]); + encode_real(v.y, &buf[sizeof(real_t)]); + encode_real(v.z, &buf[sizeof(real_t) * 2]); + buf += sizeof(real_t) * 3; } } - r_len += 4 * 3 * len; + r_len += sizeof(real_t) * 3 * len; } break; case Variant::PACKED_COLOR_ARRAY: { @@ -1497,7 +1662,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo encode_float(c.g, &buf[4]); encode_float(c.b, &buf[8]); encode_float(c.a, &buf[12]); - buf += 4 * 4; + buf += 4 * 4; // Colors should always be in single-precision. } } diff --git a/core/io/marshalls.h b/core/io/marshalls.h index c8ed497528..05804d5a46 100644 --- a/core/io/marshalls.h +++ b/core/io/marshalls.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,17 @@ #ifndef MARSHALLS_H #define MARSHALLS_H -#include "core/reference.h" +#include "core/math/math_defs.h" +#include "core/object/ref_counted.h" #include "core/typedefs.h" -#include "core/variant.h" +#include "core/variant/variant.h" + +// uintr_t is only for pairing with real_t, and we only need it in here. +#ifdef REAL_T_IS_DOUBLE +typedef uint64_t uintr_t; +#else +typedef uint32_t uintr_t; +#endif /** * Miscellaneous helpers for marshalling data types, and encoding @@ -50,6 +58,12 @@ union MarshallDouble { double d; ///< double }; +// Behaves like one of the above, depending on compilation setting. +union MarshallReal { + uintr_t i; + real_t r; +}; + static inline unsigned int encode_uint16(uint16_t p_uint, uint8_t *p_arr) { for (int i = 0; i < 2; i++) { *p_arr = p_uint & 0xFF; @@ -96,6 +110,24 @@ static inline unsigned int encode_double(double p_double, uint8_t *p_arr) { return sizeof(uint64_t); } +static inline unsigned int encode_uintr(uintr_t p_uint, uint8_t *p_arr) { + for (size_t i = 0; i < sizeof(uintr_t); i++) { + *p_arr = p_uint & 0xFF; + p_arr++; + p_uint >>= 8; + } + + return sizeof(uintr_t); +} + +static inline unsigned int encode_real(real_t p_real, uint8_t *p_arr) { + MarshallReal mr; + mr.r = p_real; + encode_uintr(mr.i, p_arr); + + return sizeof(uintr_t); +} + static inline int encode_cstring(const char *p_string, uint8_t *p_data) { int len = 0; @@ -165,8 +197,8 @@ static inline double decode_double(const uint8_t *p_arr) { return md.d; } -class EncodedObjectAsID : public Reference { - GDCLASS(EncodedObjectAsID, Reference); +class EncodedObjectAsID : public RefCounted { + GDCLASS(EncodedObjectAsID, RefCounted); ObjectID id; @@ -181,6 +213,6 @@ public: }; Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len = nullptr, bool p_allow_objects = false); -Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects = false); +Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_full_objects = false, int p_depth = 0); #endif // MARSHALLS_H diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index 2f17d9e746..c145225751 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,48 +32,63 @@ #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> -#define NODE_ID_COMPRESSION_SHIFT 3 -#define NAME_ID_COMPRESSION_SHIFT 5 -#define BYTE_ONLY_OR_NO_ARGS_SHIFT 6 - #ifdef DEBUG_ENABLED #include "core/os/os.h" #endif -_FORCE_INLINE_ bool _should_call_local(MultiplayerAPI::RPCMode mode, bool is_master, bool &r_skip_rpc) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - // Do nothing. - } break; - case MultiplayerAPI::RPC_MODE_REMOTE: { - // Do nothing also. Remote cannot produce a local call. - } break; - case MultiplayerAPI::RPC_MODE_MASTERSYNC: { - if (is_master) { - r_skip_rpc = true; // I am the master, so skip remote call. - } - [[fallthrough]]; +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); } - case MultiplayerAPI::RPC_MODE_REMOTESYNC: - case MultiplayerAPI::RPC_MODE_PUPPETSYNC: { - // Call it, sync always results in a local call. - return true; - } break; - case MultiplayerAPI::RPC_MODE_MASTER: { - if (is_master) { - r_skip_rpc = true; // I am the master, so skip remote call. + } + 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 is_master; - } break; - case MultiplayerAPI::RPC_MODE_PUPPET: { - return !is_master; - } break; + } } - return false; + 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) { @@ -81,17 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i case MultiplayerAPI::RPC_MODE_DISABLED: { return false; } break; - case MultiplayerAPI::RPC_MODE_REMOTE: - case MultiplayerAPI::RPC_MODE_REMOTESYNC: { + case MultiplayerAPI::RPC_MODE_ANY: { return true; } break; - case MultiplayerAPI::RPC_MODE_MASTERSYNC: - case MultiplayerAPI::RPC_MODE_MASTER: { - return p_node->is_network_master(); - } break; - case MultiplayerAPI::RPC_MODE_PUPPETSYNC: - case MultiplayerAPI::RPC_MODE_PUPPET: { - return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); + case MultiplayerAPI::RPC_MODE_AUTHORITY: { + return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority(); } break; } @@ -99,7 +108,7 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i } void MultiplayerAPI::poll() { - if (!network_peer.is_valid() || network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) { + if (!network_peer.is_valid() || network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) { return; } @@ -128,9 +137,13 @@ void MultiplayerAPI::poll() { 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(); @@ -142,13 +155,17 @@ void MultiplayerAPI::set_root_node(Node *p_node) { root_node = p_node; } -void MultiplayerAPI::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_peer) { +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() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED, - "Supplied NetworkedMultiplayerPeer must be connecting or connected."); + 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)); @@ -170,7 +187,7 @@ void MultiplayerAPI::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_pee } } -Ref<NetworkedMultiplayerPeer> MultiplayerAPI::get_network_peer() const { +Ref<MultiplayerPeer> MultiplayerAPI::get_network_peer() const { return network_peer; } @@ -227,8 +244,7 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ _process_confirm_path(p_from, p_packet, p_packet_len); } break; - case NETWORK_COMMAND_REMOTE_CALL: - case NETWORK_COMMAND_REMOTE_SET: { + case NETWORK_COMMAND_REMOTE_CALL: { // Extract packet meta int packet_min_size = 1; int name_id_offset = 1; @@ -298,18 +314,21 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ } const int packet_len = get_packet_len(node_target, p_packet_len); - if (packet_type == NETWORK_COMMAND_REMOTE_CALL) { - _process_rpc(node, name_id, p_from, p_packet, packet_len, packet_min_size); - - } else { - _process_rset(node, name_id, p_from, p_packet, packet_len, packet_min_size); - } - + _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; } } @@ -318,7 +337,6 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin 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."); @@ -333,41 +351,22 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, uin if (!node) { ERR_PRINT("Failed to get path from RPC: " + String(np) + "."); } + return node; } else { // Use cached path. - int id = p_node_target; - - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Invalid packet received. Requests invalid peer cache."); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id); - ERR_FAIL_COND_V_MSG(!F, nullptr, "Invalid packet received. Unabled to find requested cached node."); - - PathGetCache::NodeInfo *ni = &F->get(); - // Do proper caching later. - - node = root_node->get_node(ni->path); - if (!node) { - ERR_PRINT("Failed to get cached path from RPC: " + String(ni->path) + "."); - } + return get_cached_node(p_from, p_node_target); } - return node; } 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. - StringName name = p_node->get_node_rpc_method(p_rpc_method_id); - RPCMode rpc_mode = p_node->get_node_rpc_mode_by_id(p_rpc_method_id); - if (name == StringName() && p_node->get_script_instance()) { - name = p_node->get_script_instance()->get_rpc_method(p_rpc_method_id); - rpc_mode = p_node->get_script_instance()->get_rpc_mode_by_id(p_rpc_method_id); - } - ERR_FAIL_COND(name == StringName()); + 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, rpc_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rpc_mode) + ", master is " + itos(p_node->get_network_master()) + "."); + 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; @@ -410,7 +409,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, 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); + 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]; @@ -420,47 +419,14 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, Callable::CallError ce; - p_node->call(name, (const Variant **)argp.ptr(), argc, 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, name, (const Variant **)argp.ptr(), argc, ce); + 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_rset(Node *p_node, const uint16_t p_rpc_property_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { - ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small."); - - // Check that remote can call the RSET on this node. - StringName name = p_node->get_node_rset_property(p_rpc_property_id); - RPCMode rset_mode = p_node->get_node_rset_mode_by_id(p_rpc_property_id); - if (name == StringName() && p_node->get_script_instance()) { - name = p_node->get_script_instance()->get_rset_property(p_rpc_property_id); - rset_mode = p_node->get_script_instance()->get_rset_mode_by_id(p_rpc_property_id); - } - ERR_FAIL_COND(name == StringName()); - - bool can_call = _can_call_mode(p_node, rset_mode, p_from); - ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + "."); - -#ifdef DEBUG_ENABLED - _profile_node_data("in_rset", p_node->get_instance_id()); -#endif - - Variant value; - Error err = _decode_and_decompress_variant(value, &p_packet[p_offset], p_packet_len - p_offset, nullptr); - - ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RSET value."); - - bool valid; - - p_node->set(name, value, &valid); - if (!valid) { - String error = "Error setting remote property '" + String(name) + "', not found in object of type " + p_node->get_class() + "."; - ERR_PRINT(error); - } -} - void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); int ofs = 1; @@ -483,7 +449,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, Node *node = root_node->get_node(path); ERR_FAIL_COND(node == nullptr); - const bool valid_rpc_checksum = node->get_rpc_md5() == methods_md5; + 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); } @@ -504,7 +470,8 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, packet.write[1] = valid_rpc_checksum; encode_cstring(pname.get_data(), &packet.write[2]); - network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_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()); } @@ -565,7 +532,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC const int path_len = encode_cstring(path.get_data(), nullptr); // Extract MD5 from rpc methods list. - const String methods_md5 = p_node->get_rpc_md5(); + 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; @@ -581,12 +548,13 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC ofs += encode_cstring(path.get_data(), &packet.write[ofs]); - for (List<int>::Element *E = peers_to_add.front(); E; E = E->next()) { - network_peer->set_target_peer(E->get()); // To all of you. - network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + 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->get(), false); // Insert into confirmed, but as false since it was not confirmed. + psc->confirmed_peers.insert(E, false); // Insert into confirmed, but as false since it was not confirmed. } } @@ -605,7 +573,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC #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) { +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); @@ -671,7 +639,7 @@ Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uin return err; } if (r_buffer) { - // The first byte is not used by the marshaling, so store the type + // 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(); } @@ -680,7 +648,7 @@ Error MultiplayerAPI::_encode_and_compress_variant(const Variant &p_variant, uin return OK; } -Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) { +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; @@ -748,12 +716,12 @@ Error MultiplayerAPI::_decode_and_decompress_variant(Variant &r_variant, const u return OK; } -void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount) { +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() == NetworkedMultiplayerPeer::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_CONNECTING, "Attempt to remote call/set when networking is not connected yet in SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED, "Attempt to remote call/set when networking is disconnected."); + 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."); @@ -787,13 +755,13 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p packet_cache.resize(m_amount); // Encode meta. - // The meta is composed by a single byte that contains (starting from the least segnificant bit): + // 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 = p_set ? NETWORK_COMMAND_REMOTE_SET : NETWORK_COMMAND_REMOTE_CALL; + 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; @@ -826,88 +794,49 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p ofs += 4; } } else { - // The targets doesn't know the node yet, so we need to use 32 bits int. + // 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; } - if (p_set) { - // Take the rpc property ID - uint16_t property_id = p_from->get_node_rset_property_id(p_name); - if (property_id == UINT16_MAX && p_from->get_script_instance()) { - property_id = p_from->get_script_instance()->get_rset_property_id(p_name); - } - ERR_FAIL_COND_MSG(property_id == UINT16_MAX, "Unable to take the `property_id` for the property:" + p_name + ". this can happen only if this property is not marked as `remote`."); - - if (property_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(property_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(property_id, &(packet_cache.write[ofs])); - ofs += 2; - } - - // Set argument. - int len(0); - Error err = _encode_and_compress_variant(*p_arg[0], nullptr, len); - ERR_FAIL_COND_MSG(err != OK, "Unable to encode RSET value. THIS IS LIKELY A BUG IN THE ENGINE!"); - MAKE_ROOM(ofs + len); - _encode_and_compress_variant(*p_arg[0], &(packet_cache.write[ofs]), len); - ofs += len; - + // 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 { - // Take the rpc method ID - uint16_t method_id = p_from->get_node_rpc_method_id(p_name); - if (method_id == UINT16_MAX && p_from->get_script_instance()) { - method_id = p_from->get_script_instance()->get_rpc_method_id(p_name); - } - ERR_FAIL_COND_MSG(method_id == UINT16_MAX, - vformat("Unable to take the `method_id` for the function \"%s\" at path: \"%s\". This happens when the method is not marked as `remote`.", p_name, p_from->get_path())); - - if (method_id <= UINT8_MAX) { - // The ID fits in 1 byte - name_id_compression = NETWORK_NAME_ID_COMPRESSION_8; - MAKE_ROOM(ofs + 1); - packet_cache.write[ofs] = static_cast<uint8_t>(method_id); - ofs += 1; - } else { - // The ID is larger, let's use 2 bytes - name_id_compression = NETWORK_NAME_ID_COMPRESSION_16; - MAKE_ROOM(ofs + 2); - encode_uint16(method_id, &(packet_cache.write[ofs])); - ofs += 2; - } + // 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()); - copymem(&(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; - } + 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; } } @@ -923,7 +852,8 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p #endif // Take chance and set transfer mode, since all send methods will use it. - network_peer->set_transfer_mode(p_unreliable ? NetworkedMultiplayerPeer::TRANSFER_MODE_UNRELIABLE : NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + 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. @@ -971,7 +901,10 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p void MultiplayerAPI::_add_peer(int p_id) { connected_peers.insert(p_id); path_get_cache.insert(p_id, PathGetCache()); - emit_signal("network_peer_connected", p_id); + if (is_network_server()) { + replicator->spawn_all(p_id); + } + emit_signal(SNAME("network_peer_connected"), p_id); } void MultiplayerAPI::_del_peer(int p_id) { @@ -982,57 +915,51 @@ void MultiplayerAPI::_del_peer(int p_id) { // Some refactoring is needed to make this faster and do paths GC. List<NodePath> keys; path_send_cache.get_key_list(&keys); - for (List<NodePath>::Element *E = keys.front(); E; E = E->next()) { - PathSentCache *psc = path_send_cache.getptr(E->get()); + for (const NodePath &E : keys) { + PathSentCache *psc = path_send_cache.getptr(E); psc->confirmed_peers.erase(p_id); } - emit_signal("network_peer_disconnected", p_id); + emit_signal(SNAME("network_peer_disconnected"), p_id); } void MultiplayerAPI::_connected_to_server() { - emit_signal("connected_to_server"); + emit_signal(SNAME("connected_to_server")); } void MultiplayerAPI::_connection_failed() { - emit_signal("connection_failed"); + emit_signal(SNAME("connection_failed")); } void MultiplayerAPI::_server_disconnected() { - emit_signal("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() != NetworkedMultiplayerPeer::CONNECTION_CONNECTED, "Trying to call an RPC via a network peer which is not connected."); + 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 skip_rpc = node_id == p_peer_id; bool call_local_native = false; bool call_local_script = false; - bool is_master = p_node->is_network_master(); - + 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)) { - // Check that send mode can use local call. - - RPCMode rpc_mode = p_node->get_node_rpc_mode(p_method); - call_local_native = _should_call_local(rpc_mode, is_master, skip_rpc); - - if (call_local_native) { - // Done below. - } else if (p_node->get_script_instance()) { - // Attempt with script. - rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method); - call_local_script = _should_call_local(rpc_mode, is_master, skip_rpc); + if (rpc_id & (1 << 15)) { + call_local_native = config.sync; + } else { + call_local_script = config.sync; } } - if (!skip_rpc) { + 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, p_unreliable, false, p_method, p_arg, p_argcount); + _send_rpc(p_node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); } if (call_local_native) { @@ -1064,77 +991,13 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const } } - ERR_FAIL_COND_MSG(skip_rpc && !(call_local_native || call_local_script), "RPC '" + p_method + "' on yourself is not allowed by selected mode."); -} - -void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value) { - ERR_FAIL_COND_MSG(!network_peer.is_valid(), "Trying to RSET while no network peer is active."); - ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "Trying to RSET on a node which is not inside SceneTree."); - ERR_FAIL_COND_MSG(network_peer->get_connection_status() != NetworkedMultiplayerPeer::CONNECTION_CONNECTED, "Trying to send an RSET via a network peer which is not connected."); - - int node_id = network_peer->get_unique_id(); - bool is_master = p_node->is_network_master(); - bool skip_rset = node_id == p_peer_id; - bool set_local = false; - - if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { - // Check that send mode can use local call. - RPCMode rpc_mode = p_node->get_node_rset_mode(p_property); - set_local = _should_call_local(rpc_mode, is_master, skip_rset); - - if (set_local) { - bool valid; - int temp_id = rpc_sender_id; - - rpc_sender_id = get_network_unique_id(); - p_node->set(p_property, p_value, &valid); - rpc_sender_id = temp_id; - - if (!valid) { - String error = "rset() aborted in local set, property not found: - " + String(p_property) + "."; - ERR_PRINT(error); - return; - } - } else if (p_node->get_script_instance()) { - // Attempt with script. - rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property); - - set_local = _should_call_local(rpc_mode, is_master, skip_rset); - - if (set_local) { - int temp_id = rpc_sender_id; - - rpc_sender_id = get_network_unique_id(); - bool valid = p_node->get_script_instance()->set(p_property, p_value); - rpc_sender_id = temp_id; - - if (!valid) { - String error = "rset() aborted in local script set, property not found: - " + String(p_property) + "."; - ERR_PRINT(error); - return; - } - } - } - } - - if (skip_rset) { - ERR_FAIL_COND_MSG(!set_local, "RSET for '" + p_property + "' on yourself is not allowed by selected mode."); - return; - } - -#ifdef DEBUG_ENABLED - _profile_node_data("out_rset", p_node->get_instance_id()); -#endif - - const Variant *vptr = &p_value; - - _send_rpc(p_node, p_peer_id, p_unreliable, true, p_property, &vptr, 1); + 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, NetworkedMultiplayerPeer::TransferMode p_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() != NetworkedMultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected."); + 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(); @@ -1142,6 +1005,7 @@ Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, NetworkedMult 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); @@ -1157,7 +1021,37 @@ void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_pac uint8_t *w = out.ptrw(); memcpy(&w[0], &p_packet[1], len); } - emit_signal("network_peer_packet", p_from, out); + 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 { @@ -1166,9 +1060,7 @@ int MultiplayerAPI::get_network_unique_id() const { } bool MultiplayerAPI::is_network_server() const { - // XXX Maybe fail silently? Maybe should actually return true to make development of both local and online multiplayer easier? - ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), false, "No network peer is assigned. I can't be a server."); - return network_peer->is_server(); + return network_peer.is_valid() && network_peer->is_server(); } void MultiplayerAPI::set_refuse_new_network_connections(bool p_refuse) { @@ -1200,9 +1092,18 @@ 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("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE)); + 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); @@ -1217,11 +1118,14 @@ void MultiplayerAPI::_bind_methods() { 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, "NetworkedMultiplayerPeer", 0), "set_network_peer", "get_network_peer"); + 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"))); @@ -1231,18 +1135,16 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("server_disconnected")); BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_PUPPET); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTESYNC); - BIND_ENUM_CONSTANT(RPC_MODE_MASTERSYNC); - BIND_ENUM_CONSTANT(RPC_MODE_PUPPETSYNC); + 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 index 06eab7796c..3c96a3eed1 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,11 +31,67 @@ #ifndef MULTIPLAYER_API_H #define MULTIPLAYER_API_H -#include "core/io/networked_multiplayer_peer.h" -#include "core/reference.h" +#include "core/io/multiplayer_peer.h" +#include "core/io/resource_uid.h" +#include "core/object/ref_counted.h" -class MultiplayerAPI : public Reference { - GDCLASS(MultiplayerAPI, Reference); +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 @@ -54,7 +110,7 @@ private: Map<int, NodeInfo> nodes; }; - Ref<NetworkedMultiplayerPeer> network_peer; + Ref<MultiplayerPeer> network_peer; int rpc_sender_id = 0; Set<int> connected_peers; HashMap<NodePath, PathSentCache> path_send_cache; @@ -63,6 +119,7 @@ private: Vector<uint8_t> packet_cache; Node *root_node = nullptr; bool allow_object_decoding = false; + MultiplayerReplicator *replicator = nullptr; protected: static void _bind_methods(); @@ -72,57 +129,30 @@ protected: 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_rset(Node *p_node, const uint16_t p_rpc_property_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); - void _send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount); + 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); - Error _encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len); - Error _decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len); - public: - enum NetworkCommands { - NETWORK_COMMAND_REMOTE_CALL = 0, - NETWORK_COMMAND_REMOTE_SET, - NETWORK_COMMAND_SIMPLIFY_PATH, - NETWORK_COMMAND_CONFIRM_PATH, - NETWORK_COMMAND_RAW, - }; - - enum NetworkNodeIdCompression { - NETWORK_NODE_ID_COMPRESSION_8 = 0, - NETWORK_NODE_ID_COMPRESSION_16, - NETWORK_NODE_ID_COMPRESSION_32, - }; - - enum NetworkNameIdCompression { - NETWORK_NAME_ID_COMPRESSION_8 = 0, - NETWORK_NAME_ID_COMPRESSION_16, - }; - - enum RPCMode { - - RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) - RPC_MODE_REMOTE, // Using rpc() on it will call method / set property in all remote peers - RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets - RPC_MODE_REMOTESYNC, // Using rpc() on it will call method / set property in all remote peers and locally - RPC_MODE_MASTERSYNC, // Using rpc() on it will call method / set property in the master peer and locally - RPC_MODE_PUPPETSYNC, // Using rpc() on it will call method / set property in all puppets peers and locally - }; - void poll(); void clear(); void set_root_node(Node *p_node); - void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_peer); - Ref<NetworkedMultiplayerPeer> get_network_peer() const; - Error send_bytes(Vector<uint8_t> p_data, int p_to = NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST, NetworkedMultiplayerPeer::TransferMode p_mode = NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + 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.rset - void rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value); + // 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); @@ -141,6 +171,8 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; + MultiplayerReplicator *get_replicator() const; + MultiplayerAPI(); ~MultiplayerAPI(); }; diff --git a/core/io/networked_multiplayer_peer.cpp b/core/io/multiplayer_peer.cpp index f521f2bb79..83cf24d7e3 100644 --- a/core/io/networked_multiplayer_peer.cpp +++ b/core/io/multiplayer_peer.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* networked_multiplayer_peer.cpp */ +/* multiplayer_peer.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,25 +28,52 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "networked_multiplayer_peer.h" +#include "multiplayer_peer.h" -void NetworkedMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &NetworkedMultiplayerPeer::set_transfer_mode); - ClassDB::bind_method(D_METHOD("get_transfer_mode"), &NetworkedMultiplayerPeer::get_transfer_mode); - ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &NetworkedMultiplayerPeer::set_target_peer); +#include "core/os/os.h" - ClassDB::bind_method(D_METHOD("get_packet_peer"), &NetworkedMultiplayerPeer::get_packet_peer); +uint32_t MultiplayerPeer::generate_unique_id() const { + uint32_t hash = 0; - ClassDB::bind_method(D_METHOD("poll"), &NetworkedMultiplayerPeer::poll); + 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 - ClassDB::bind_method(D_METHOD("get_connection_status"), &NetworkedMultiplayerPeer::get_connection_status); - ClassDB::bind_method(D_METHOD("get_unique_id"), &NetworkedMultiplayerPeer::get_unique_id); + hash = hash & 0x7FFFFFFF; // Make it compatible with unsigned, since negative ID is used for exclusion + } - ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "enable"), &NetworkedMultiplayerPeer::set_refuse_new_connections); - ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &NetworkedMultiplayerPeer::is_refusing_new_connections); + 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); diff --git a/core/io/networked_multiplayer_peer.h b/core/io/multiplayer_peer.h index dc76237f45..7ca4e7930b 100644 --- a/core/io/networked_multiplayer_peer.h +++ b/core/io/multiplayer_peer.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* networked_multiplayer_peer.h */ +/* multiplayer_peer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,8 +33,8 @@ #include "core/io/packet_peer.h" -class NetworkedMultiplayerPeer : public PacketPeer { - GDCLASS(NetworkedMultiplayerPeer, PacketPeer); +class MultiplayerPeer : public PacketPeer { + GDCLASS(MultiplayerPeer, PacketPeer); protected: static void _bind_methods(); @@ -56,6 +56,8 @@ public: 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; @@ -72,11 +74,12 @@ public: virtual bool is_refusing_new_connections() const = 0; virtual ConnectionStatus get_connection_status() const = 0; + uint32_t generate_unique_id() const; - NetworkedMultiplayerPeer() {} + MultiplayerPeer() {} }; -VARIANT_ENUM_CAST(NetworkedMultiplayerPeer::TransferMode) -VARIANT_ENUM_CAST(NetworkedMultiplayerPeer::ConnectionStatus) +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 new file mode 100644 index 0000000000..1642aab136 --- /dev/null +++ b/core/io/multiplayer_replicator.cpp @@ -0,0 +1,786 @@ +/*************************************************************************/ +/* 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; + } 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 new file mode 100644 index 0000000000..2630ad7a8a --- /dev/null +++ b/core/io/multiplayer_replicator.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/* 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/net_socket.cpp b/core/io/net_socket.cpp index 130a2e245e..b51d26ba83 100644 --- a/core/io/net_socket.cpp +++ b/core/io/net_socket.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/core/io/net_socket.h b/core/io/net_socket.h index 746945eced..fd7d50c704 100644 --- a/core/io/net_socket.h +++ b/core/io/net_socket.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,9 +32,9 @@ #define NET_SOCKET_H #include "core/io/ip.h" -#include "core/reference.h" +#include "core/object/ref_counted.h" -class NetSocket : public Reference { +class NetSocket : public RefCounted { protected: static NetSocket *(*_create)(); @@ -55,26 +55,27 @@ public: virtual Error open(Type p_type, IP::Type &ip_type) = 0; virtual void close() = 0; - virtual Error bind(IP_Address p_addr, uint16_t p_port) = 0; + virtual Error bind(IPAddress p_addr, uint16_t p_port) = 0; virtual Error listen(int p_max_pending) = 0; - virtual Error connect_to_host(IP_Address p_addr, uint16_t p_port) = 0; + virtual Error connect_to_host(IPAddress p_addr, uint16_t p_port) = 0; virtual Error poll(PollType p_type, int timeout) const = 0; virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) = 0; - virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Address &r_ip, uint16_t &r_port, bool p_peek = false) = 0; + virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) = 0; virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) = 0; - virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP_Address p_ip, uint16_t p_port) = 0; - virtual Ref<NetSocket> accept(IP_Address &r_ip, uint16_t &r_port) = 0; + virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) = 0; + virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) = 0; virtual bool is_open() const = 0; virtual int get_available_bytes() const = 0; + virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const = 0; virtual Error set_broadcasting_enabled(bool p_enabled) = 0; // Returns OK if the socket option has been set successfully. virtual void set_blocking_enabled(bool p_enabled) = 0; virtual void set_ipv6_only_enabled(bool p_enabled) = 0; virtual void set_tcp_no_delay_enabled(bool p_enabled) = 0; virtual void set_reuse_address_enabled(bool p_enabled) = 0; - virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name) = 0; - virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) = 0; + virtual Error join_multicast_group(const IPAddress &p_multi_address, String p_if_name) = 0; + virtual Error leave_multicast_group(const IPAddress &p_multi_address, String p_if_name) = 0; }; #endif // NET_SOCKET_H diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp new file mode 100644 index 0000000000..4a76f0191d --- /dev/null +++ b/core/io/packed_data_container.cpp @@ -0,0 +1,400 @@ +/*************************************************************************/ +/* packed_data_container.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 "packed_data_container.h" + +#include "core/core_string_names.h" +#include "core/io/marshalls.h" + +Variant PackedDataContainer::getvar(const Variant &p_key, bool *r_valid) const { + bool err = false; + Variant ret = _key_at_ofs(0, p_key, err); + if (r_valid) { + *r_valid = !err; + } + if (err) { + return Object::getvar(p_key, r_valid); + } + return ret; +} + +int PackedDataContainer::size() const { + return _size(0); +} + +Variant PackedDataContainer::_iter_init_ofs(const Array &p_iter, uint32_t p_offset) { + Array ref = p_iter; + uint32_t size = _size(p_offset); + if (size == 0 || ref.size() != 1) { + return false; + } else { + ref[0] = 0; + return true; + } +} + +Variant PackedDataContainer::_iter_next_ofs(const Array &p_iter, uint32_t p_offset) { + Array ref = p_iter; + int size = _size(p_offset); + if (ref.size() != 1) { + return false; + } + int pos = ref[0]; + if (pos < 0 || pos >= size) { + return false; + } + pos += 1; + ref[0] = pos; + return pos != size; +} + +Variant PackedDataContainer::_iter_get_ofs(const Variant &p_iter, uint32_t p_offset) { + int size = _size(p_offset); + int pos = p_iter; + if (pos < 0 || pos >= size) { + return Variant(); + } + + const uint8_t *rd = data.ptr(); + const uint8_t *r = &rd[p_offset]; + uint32_t type = decode_uint32(r); + + bool err = false; + if (type == TYPE_ARRAY) { + uint32_t vpos = decode_uint32(rd + p_offset + 8 + pos * 4); + return _get_at_ofs(vpos, rd, err); + + } else if (type == TYPE_DICT) { + uint32_t vpos = decode_uint32(rd + p_offset + 8 + pos * 12 + 4); + return _get_at_ofs(vpos, rd, err); + } else { + ERR_FAIL_V(Variant()); + } +} + +Variant PackedDataContainer::_get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, bool &err) const { + uint32_t type = decode_uint32(p_buf + p_ofs); + + if (type == TYPE_ARRAY || type == TYPE_DICT) { + Ref<PackedDataContainerRef> pdcr = memnew(PackedDataContainerRef); + Ref<PackedDataContainer> pdc = Ref<PackedDataContainer>((PackedDataContainer *)this); + + pdcr->from = pdc; + pdcr->offset = p_ofs; + return pdcr; + } else { + Variant v; + Error rerr = decode_variant(v, p_buf + p_ofs, datalen - p_ofs, nullptr, false); + + if (rerr != OK) { + err = true; + ERR_FAIL_COND_V_MSG(err != OK, Variant(), "Error when trying to decode Variant."); + } + return v; + } +} + +uint32_t PackedDataContainer::_type_at_ofs(uint32_t p_ofs) const { + const uint8_t *rd = data.ptr(); + ERR_FAIL_COND_V(!rd, 0); + const uint8_t *r = &rd[p_ofs]; + uint32_t type = decode_uint32(r); + + return type; +} + +int PackedDataContainer::_size(uint32_t p_ofs) const { + const uint8_t *rd = data.ptr(); + ERR_FAIL_COND_V(!rd, 0); + const uint8_t *r = &rd[p_ofs]; + uint32_t type = decode_uint32(r); + + if (type == TYPE_ARRAY) { + uint32_t len = decode_uint32(r + 4); + return len; + + } else if (type == TYPE_DICT) { + uint32_t len = decode_uint32(r + 4); + return len; + } + + return -1; +} + +Variant PackedDataContainer::_key_at_ofs(uint32_t p_ofs, const Variant &p_key, bool &err) const { + const uint8_t *rd = data.ptr(); + if (!rd) { + err = true; + ERR_FAIL_COND_V(!rd, Variant()); + } + const uint8_t *r = &rd[p_ofs]; + uint32_t type = decode_uint32(r); + + if (type == TYPE_ARRAY) { + if (p_key.is_num()) { + int idx = p_key; + int len = decode_uint32(r + 4); + if (idx < 0 || idx >= len) { + err = true; + return Variant(); + } + uint32_t ofs = decode_uint32(r + 8 + 4 * idx); + return _get_at_ofs(ofs, rd, err); + + } else { + err = true; + return Variant(); + } + + } else if (type == TYPE_DICT) { + uint32_t hash = p_key.hash(); + uint32_t len = decode_uint32(r + 4); + + bool found = false; + for (uint32_t i = 0; i < len; i++) { + uint32_t khash = decode_uint32(r + 8 + i * 12 + 0); + if (khash == hash) { + Variant key = _get_at_ofs(decode_uint32(r + 8 + i * 12 + 4), rd, err); + if (err) { + return Variant(); + } + if (key == p_key) { + //key matches, return value + return _get_at_ofs(decode_uint32(r + 8 + i * 12 + 8), rd, err); + } + found = true; + } else { + if (found) { + break; + } + } + } + + err = true; + return Variant(); + + } else { + err = true; + return Variant(); + } +} + +uint32_t PackedDataContainer::_pack(const Variant &p_data, Vector<uint8_t> &tmpdata, Map<String, uint32_t> &string_cache) { + switch (p_data.get_type()) { + case Variant::STRING: { + String s = p_data; + if (string_cache.has(s)) { + return string_cache[s]; + } + + string_cache[s] = tmpdata.size(); + + [[fallthrough]]; + } + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: + case Variant::VECTOR2: + case Variant::RECT2: + case Variant::VECTOR3: + case Variant::TRANSFORM2D: + case Variant::PLANE: + case Variant::QUATERNION: + case Variant::AABB: + case Variant::BASIS: + case Variant::TRANSFORM3D: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::STRING_NAME: + case Variant::NODE_PATH: { + uint32_t pos = tmpdata.size(); + int len; + encode_variant(p_data, nullptr, len, false); + tmpdata.resize(tmpdata.size() + len); + encode_variant(p_data, &tmpdata.write[pos], len, false); + return pos; + + } break; + // misc types + case Variant::RID: + case Variant::OBJECT: { + return _pack(Variant(), tmpdata, string_cache); + } break; + case Variant::DICTIONARY: { + Dictionary d = p_data; + //size is known, use sort + uint32_t pos = tmpdata.size(); + int len = d.size(); + tmpdata.resize(tmpdata.size() + len * 12 + 8); + encode_uint32(TYPE_DICT, &tmpdata.write[pos + 0]); + encode_uint32(len, &tmpdata.write[pos + 4]); + + List<Variant> keys; + d.get_key_list(&keys); + List<DictKey> sortk; + + for (const Variant &key : keys) { + DictKey dk; + dk.hash = key.hash(); + dk.key = key; + sortk.push_back(dk); + } + + sortk.sort(); + + int idx = 0; + for (const DictKey &E : sortk) { + encode_uint32(E.hash, &tmpdata.write[pos + 8 + idx * 12 + 0]); + uint32_t ofs = _pack(E.key, tmpdata, string_cache); + encode_uint32(ofs, &tmpdata.write[pos + 8 + idx * 12 + 4]); + ofs = _pack(d[E.key], tmpdata, string_cache); + encode_uint32(ofs, &tmpdata.write[pos + 8 + idx * 12 + 8]); + idx++; + } + + return pos; + + } break; + case Variant::ARRAY: { + Array a = p_data; + //size is known, use sort + uint32_t pos = tmpdata.size(); + int len = a.size(); + tmpdata.resize(tmpdata.size() + len * 4 + 8); + encode_uint32(TYPE_ARRAY, &tmpdata.write[pos + 0]); + encode_uint32(len, &tmpdata.write[pos + 4]); + + for (int i = 0; i < len; i++) { + uint32_t ofs = _pack(a[i], tmpdata, string_cache); + encode_uint32(ofs, &tmpdata.write[pos + 8 + i * 4]); + } + + return pos; + + } break; + + default: { + } + } + + return OK; +} + +Error PackedDataContainer::pack(const Variant &p_data) { + Vector<uint8_t> tmpdata; + Map<String, uint32_t> string_cache; + _pack(p_data, tmpdata, string_cache); + datalen = tmpdata.size(); + data.resize(tmpdata.size()); + uint8_t *w = data.ptrw(); + memcpy(w, tmpdata.ptr(), tmpdata.size()); + + return OK; +} + +void PackedDataContainer::_set_data(const Vector<uint8_t> &p_data) { + data = p_data; + datalen = data.size(); +} + +Vector<uint8_t> PackedDataContainer::_get_data() const { + return data; +} + +Variant PackedDataContainer::_iter_init(const Array &p_iter) { + return _iter_init_ofs(p_iter, 0); +} + +Variant PackedDataContainer::_iter_next(const Array &p_iter) { + return _iter_next_ofs(p_iter, 0); +} + +Variant PackedDataContainer::_iter_get(const Variant &p_iter) { + return _iter_get_ofs(p_iter, 0); +} + +void PackedDataContainer::_bind_methods() { + ClassDB::bind_method(D_METHOD("_set_data"), &PackedDataContainer::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &PackedDataContainer::_get_data); + ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainer::_iter_init); + ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainer::_iter_get); + ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainer::_iter_next); + ClassDB::bind_method(D_METHOD("pack", "value"), &PackedDataContainer::pack); + ClassDB::bind_method(D_METHOD("size"), &PackedDataContainer::size); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__"), "_set_data", "_get_data"); +} + +////////////////// + +Variant PackedDataContainerRef::_iter_init(const Array &p_iter) { + return from->_iter_init_ofs(p_iter, offset); +} + +Variant PackedDataContainerRef::_iter_next(const Array &p_iter) { + return from->_iter_next_ofs(p_iter, offset); +} + +Variant PackedDataContainerRef::_iter_get(const Variant &p_iter) { + return from->_iter_get_ofs(p_iter, offset); +} + +bool PackedDataContainerRef::_is_dictionary() const { + return from->_type_at_ofs(offset) == PackedDataContainer::TYPE_DICT; +} + +void PackedDataContainerRef::_bind_methods() { + ClassDB::bind_method(D_METHOD("size"), &PackedDataContainerRef::size); + ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainerRef::_iter_init); + ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainerRef::_iter_get); + ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainerRef::_iter_next); + ClassDB::bind_method(D_METHOD("_is_dictionary"), &PackedDataContainerRef::_is_dictionary); +} + +Variant PackedDataContainerRef::getvar(const Variant &p_key, bool *r_valid) const { + bool err = false; + Variant ret = from->_key_at_ofs(offset, p_key, err); + if (r_valid) { + *r_valid = !err; + } + return ret; +} + +int PackedDataContainerRef::size() const { + return from->_size(offset); +} diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h new file mode 100644 index 0000000000..40772bb2bf --- /dev/null +++ b/core/io/packed_data_container.h @@ -0,0 +1,105 @@ +/*************************************************************************/ +/* packed_data_container.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 PACKED_DATA_CONTAINER_H +#define PACKED_DATA_CONTAINER_H + +#include "core/io/resource.h" + +class PackedDataContainer : public Resource { + GDCLASS(PackedDataContainer, Resource); + + enum { + TYPE_DICT = 0xFFFFFFFF, + TYPE_ARRAY = 0xFFFFFFFE, + }; + + struct DictKey { + uint32_t hash; + Variant key; + bool operator<(const DictKey &p_key) const { return hash < p_key.hash; } + }; + + Vector<uint8_t> data; + int datalen = 0; + + uint32_t _pack(const Variant &p_data, Vector<uint8_t> &tmpdata, Map<String, uint32_t> &string_cache); + + Variant _iter_init_ofs(const Array &p_iter, uint32_t p_offset); + Variant _iter_next_ofs(const Array &p_iter, uint32_t p_offset); + Variant _iter_get_ofs(const Variant &p_iter, uint32_t p_offset); + + Variant _iter_init(const Array &p_iter); + Variant _iter_next(const Array &p_iter); + Variant _iter_get(const Variant &p_iter); + + friend class PackedDataContainerRef; + Variant _key_at_ofs(uint32_t p_ofs, const Variant &p_key, bool &err) const; + Variant _get_at_ofs(uint32_t p_ofs, const uint8_t *p_buf, bool &err) const; + uint32_t _type_at_ofs(uint32_t p_ofs) const; + int _size(uint32_t p_ofs) const; + +protected: + void _set_data(const Vector<uint8_t> &p_data); + Vector<uint8_t> _get_data() const; + static void _bind_methods(); + +public: + virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; + Error pack(const Variant &p_data); + + int size() const; + + PackedDataContainer() {} +}; + +class PackedDataContainerRef : public RefCounted { + GDCLASS(PackedDataContainerRef, RefCounted); + + friend class PackedDataContainer; + uint32_t offset = 0; + Ref<PackedDataContainer> from; + +protected: + static void _bind_methods(); + +public: + Variant _iter_init(const Array &p_iter); + Variant _iter_next(const Array &p_iter); + Variant _iter_get(const Variant &p_iter); + bool _is_dictionary() const; + + int size() const; + virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; + + PackedDataContainerRef() {} +}; + +#endif // PACKED_DATA_CONTAINER_H diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index dacd548a3e..8da44fd290 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,8 +30,8 @@ #include "packet_peer.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" -#include "core/project_settings.h" /* helpers / binders */ @@ -151,11 +151,6 @@ void PacketPeer::_bind_methods() { /***************/ -void PacketPeerStream::_set_stream_peer(REF p_peer) { - ERR_FAIL_COND_MSG(p_peer.is_null(), "It's not a reference to a valid Resource object."); - set_stream_peer(p_peer); -} - 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); @@ -166,7 +161,7 @@ void PacketPeerStream::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "input_buffer_max_size"), "set_input_buffer_max_size", "get_input_buffer_max_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "output_buffer_max_size"), "set_output_buffer_max_size", "get_output_buffer_max_size"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream_peer", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", 0), "set_stream_peer", "get_stream_peer"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream_peer", PROPERTY_HINT_RESOURCE_TYPE, "StreamPeer", PROPERTY_USAGE_NONE), "set_stream_peer", "get_stream_peer"); } Error PacketPeerStream::_poll_buffer() const { @@ -263,10 +258,8 @@ int PacketPeerStream::get_max_packet_size() const { } void PacketPeerStream::set_stream_peer(const Ref<StreamPeer> &p_peer) { - //ERR_FAIL_COND(p_peer.is_null()); - if (p_peer.ptr() != peer.ptr()) { - ring_buffer.advance_read(ring_buffer.data_left()); // reset the ring buffer + ring_buffer.advance_read(ring_buffer.data_left()); // Reset the ring buffer. } peer = p_peer; diff --git a/core/io/packet_peer.h b/core/io/packet_peer.h index 92cdbc4151..9a345af3d0 100644 --- a/core/io/packet_peer.h +++ b/core/io/packet_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,11 +32,11 @@ #define PACKET_PEER_H #include "core/io/stream_peer.h" -#include "core/object.h" -#include "core/ring_buffer.h" +#include "core/object/class_db.h" +#include "core/templates/ring_buffer.h" -class PacketPeer : public Reference { - GDCLASS(PacketPeer, Reference); +class PacketPeer : public RefCounted { + GDCLASS(PacketPeer, RefCounted); Variant _bnd_get_var(bool p_allow_objects = false); @@ -86,7 +86,6 @@ class PacketPeerStream : public PacketPeer { Error _poll_buffer() const; protected: - void _set_stream_peer(REF p_peer); static void _bind_methods(); public: diff --git a/core/io/packet_peer_dtls.cpp b/core/io/packet_peer_dtls.cpp index 632f86a9f6..a6d220622b 100644 --- a/core/io/packet_peer_dtls.cpp +++ b/core/io/packet_peer_dtls.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,8 +29,8 @@ /*************************************************************************/ #include "packet_peer_dtls.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" +#include "core/io/file_access.h" PacketPeerDTLS *(*PacketPeerDTLS::_create)() = nullptr; bool PacketPeerDTLS::available = false; diff --git a/core/io/packet_peer_dtls.h b/core/io/packet_peer_dtls.h index c2ff4e1a7f..31c52f539f 100644 --- a/core/io/packet_peer_dtls.h +++ b/core/io/packet_peer_dtls.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index e633a56d54..f951a5158c 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -45,7 +45,7 @@ void PacketPeerUDP::set_broadcast_enabled(bool p_enabled) { } } -Error PacketPeerUDP::join_multicast_group(IP_Address p_multi_address, String p_if_name) { +Error PacketPeerUDP::join_multicast_group(IPAddress p_multi_address, String p_if_name) { ERR_FAIL_COND_V(udp_server, ERR_LOCKED); ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!p_multi_address.is_valid(), ERR_INVALID_PARAMETER); @@ -60,7 +60,7 @@ Error PacketPeerUDP::join_multicast_group(IP_Address p_multi_address, String p_i return _sock->join_multicast_group(p_multi_address, p_if_name); } -Error PacketPeerUDP::leave_multicast_group(IP_Address p_multi_address, String p_if_name) { +Error PacketPeerUDP::leave_multicast_group(IPAddress p_multi_address, String p_if_name) { ERR_FAIL_COND_V(udp_server, ERR_LOCKED); ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!_sock->is_open(), ERR_UNCONFIGURED); @@ -72,7 +72,7 @@ String PacketPeerUDP::_get_packet_ip() const { } Error PacketPeerUDP::_set_dest_address(const String &p_address, int p_port) { - IP_Address ip; + IPAddress ip; if (p_address.is_valid_ip_address()) { ip = p_address; } else { @@ -159,10 +159,11 @@ int PacketPeerUDP::get_max_packet_size() const { return 512; // uhm maybe not } -Error PacketPeerUDP::listen(int p_port, const IP_Address &p_bind_address, int p_recv_buffer_size) { +Error PacketPeerUDP::bind(int p_port, const IPAddress &p_bind_address, int p_recv_buffer_size) { ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive)."); Error err; IP::Type ip_type = IP::TYPE_ANY; @@ -178,7 +179,6 @@ Error PacketPeerUDP::listen(int p_port, const IP_Address &p_bind_address, int p_ } _sock->set_blocking_enabled(false); - _sock->set_reuse_address_enabled(true); _sock->set_broadcasting_enabled(broadcast); err = _sock->bind(p_bind_address, p_port); @@ -190,7 +190,7 @@ Error PacketPeerUDP::listen(int p_port, const IP_Address &p_bind_address, int p_ return OK; } -Error PacketPeerUDP::connect_shared_socket(Ref<NetSocket> p_sock, IP_Address p_ip, uint16_t p_port, UDPServer *p_server) { +Error PacketPeerUDP::connect_shared_socket(Ref<NetSocket> p_sock, IPAddress p_ip, uint16_t p_port, UDPServer *p_server) { udp_server = p_server; connected = true; _sock = p_sock; @@ -207,10 +207,11 @@ void PacketPeerUDP::disconnect_shared_socket() { close(); } -Error PacketPeerUDP::connect_to_host(const IP_Address &p_host, int p_port) { +Error PacketPeerUDP::connect_to_host(const IPAddress &p_host, int p_port) { ERR_FAIL_COND_V(udp_server, ERR_LOCKED); ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, ERR_INVALID_PARAMETER, "The remote port number must be between 1 and 65535 (inclusive)."); Error err; @@ -225,7 +226,7 @@ Error PacketPeerUDP::connect_to_host(const IP_Address &p_host, int p_port) { // I see no reason why we should get ERR_BUSY (wouldblock/eagain) here. // This is UDP, so connect is only used to tell the OS to which socket - // it shuold deliver packets when multiple are bound on the same address/port. + // it should deliver packets when multiple are bound on the same address/port. if (err != OK) { close(); ERR_FAIL_V_MSG(FAILED, "Unable to connect"); @@ -275,7 +276,7 @@ Error PacketPeerUDP::_poll() { Error err; int read; - IP_Address ip; + IPAddress ip; uint16_t port; while (true) { @@ -305,7 +306,7 @@ Error PacketPeerUDP::_poll() { return OK; } -Error PacketPeerUDP::store_packet(IP_Address p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size) { +Error PacketPeerUDP::store_packet(IPAddress p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size) { if (rb.space_left() < p_buf_size + 24) { return ERR_OUT_OF_MEMORY; } @@ -317,11 +318,11 @@ Error PacketPeerUDP::store_packet(IP_Address p_ip, uint32_t p_port, uint8_t *p_b return OK; } -bool PacketPeerUDP::is_listening() const { +bool PacketPeerUDP::is_bound() const { return _sock.is_valid() && _sock->is_open(); } -IP_Address PacketPeerUDP::get_packet_address() const { +IPAddress PacketPeerUDP::get_packet_address() const { return packet_ip; } @@ -329,21 +330,28 @@ int PacketPeerUDP::get_packet_port() const { return packet_port; } -void PacketPeerUDP::set_dest_address(const IP_Address &p_address, int p_port) { +int PacketPeerUDP::get_local_port() const { + uint16_t local_port; + _sock->get_socket_address(nullptr, &local_port); + return local_port; +} + +void PacketPeerUDP::set_dest_address(const IPAddress &p_address, int p_port) { ERR_FAIL_COND_MSG(connected, "Destination address cannot be set for connected sockets"); peer_addr = p_address; peer_port = p_port; } void PacketPeerUDP::_bind_methods() { - ClassDB::bind_method(D_METHOD("listen", "port", "bind_address", "recv_buf_size"), &PacketPeerUDP::listen, DEFVAL("*"), DEFVAL(65536)); + ClassDB::bind_method(D_METHOD("bind", "port", "bind_address", "recv_buf_size"), &PacketPeerUDP::bind, DEFVAL("*"), DEFVAL(65536)); ClassDB::bind_method(D_METHOD("close"), &PacketPeerUDP::close); ClassDB::bind_method(D_METHOD("wait"), &PacketPeerUDP::wait); - ClassDB::bind_method(D_METHOD("is_listening"), &PacketPeerUDP::is_listening); + ClassDB::bind_method(D_METHOD("is_bound"), &PacketPeerUDP::is_bound); ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &PacketPeerUDP::connect_to_host); ClassDB::bind_method(D_METHOD("is_connected_to_host"), &PacketPeerUDP::is_connected_to_host); ClassDB::bind_method(D_METHOD("get_packet_ip"), &PacketPeerUDP::_get_packet_ip); ClassDB::bind_method(D_METHOD("get_packet_port"), &PacketPeerUDP::get_packet_port); + ClassDB::bind_method(D_METHOD("get_local_port"), &PacketPeerUDP::get_local_port); ClassDB::bind_method(D_METHOD("set_dest_address", "host", "port"), &PacketPeerUDP::_set_dest_address); ClassDB::bind_method(D_METHOD("set_broadcast_enabled", "enabled"), &PacketPeerUDP::set_broadcast_enabled); ClassDB::bind_method(D_METHOD("join_multicast_group", "multicast_address", "interface_name"), &PacketPeerUDP::join_multicast_group); diff --git a/core/io/packet_peer_udp.h b/core/io/packet_peer_udp.h index 9a44a1ebea..40d3c44e40 100644 --- a/core/io/packet_peer_udp.h +++ b/core/io/packet_peer_udp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -48,11 +48,11 @@ protected: RingBuffer<uint8_t> rb; uint8_t recv_buffer[PACKET_BUFFER_SIZE]; uint8_t packet_buffer[PACKET_BUFFER_SIZE]; - IP_Address packet_ip; + IPAddress packet_ip; int packet_port = 0; int queue_count = 0; - IP_Address peer_addr; + IPAddress peer_addr; int peer_port = 0; bool connected = false; bool blocking = true; @@ -70,28 +70,29 @@ protected: public: void set_blocking_mode(bool p_enable); - Error listen(int p_port, const IP_Address &p_bind_address = IP_Address("*"), int p_recv_buffer_size = 65536); + Error bind(int p_port, const IPAddress &p_bind_address = IPAddress("*"), int p_recv_buffer_size = 65536); void close(); Error wait(); - bool is_listening() const; + bool is_bound() const; - Error connect_shared_socket(Ref<NetSocket> p_sock, IP_Address p_ip, uint16_t p_port, UDPServer *ref); // Used by UDPServer + Error connect_shared_socket(Ref<NetSocket> p_sock, IPAddress p_ip, uint16_t p_port, UDPServer *ref); // Used by UDPServer void disconnect_shared_socket(); // Used by UDPServer - Error store_packet(IP_Address p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size); // Used internally and by UDPServer - Error connect_to_host(const IP_Address &p_host, int p_port); + Error store_packet(IPAddress p_ip, uint32_t p_port, uint8_t *p_buf, int p_buf_size); // Used internally and by UDPServer + Error connect_to_host(const IPAddress &p_host, int p_port); bool is_connected_to_host() const; - IP_Address get_packet_address() const; + IPAddress get_packet_address() const; int get_packet_port() const; - void set_dest_address(const IP_Address &p_address, int p_port); + int get_local_port() const; + void set_dest_address(const IPAddress &p_address, int p_port); Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override; Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; int get_available_packet_count() const override; int get_max_packet_size() const override; void set_broadcast_enabled(bool p_enabled); - Error join_multicast_group(IP_Address p_multi_address, String p_if_name); - Error leave_multicast_group(IP_Address p_multi_address, String p_if_name); + Error join_multicast_group(IPAddress p_multi_address, String p_if_name); + Error leave_multicast_group(IPAddress p_multi_address, String p_if_name); PacketPeerUDP(); ~PacketPeerUDP(); diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index 5480d3c535..806a95398f 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,9 @@ #include "pck_packer.h" #include "core/crypto/crypto_core.h" +#include "core/io/file_access.h" #include "core/io/file_access_encrypted.h" #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION -#include "core/os/file_access.h" #include "core/version.h" static int _get_pad(int p_alignment, int p_n) { @@ -53,7 +53,7 @@ void PCKPacker::_bind_methods() { } 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.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_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)."); String _key = p_key.to_lower(); key.resize(32); @@ -120,7 +120,7 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr pf.path = p_file; pf.src_path = p_src; pf.ofs = ofs; - pf.size = f->get_len(); + pf.size = f->get_length(); Vector<uint8_t> data = FileAccess::get_file_as_array(p_src); { @@ -236,7 +236,7 @@ Error PCKPacker::flush(bool p_verbose) { } while (to_write > 0) { - int read = src->get_buffer(buf, MIN(to_write, buf_max)); + uint64_t read = src->get_buffer(buf, MIN(to_write, buf_max)); ftmp->store_buffer(buf, read); to_write -= read; } diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h index a6054dff2c..3d2ce8f240 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,15 +31,15 @@ #ifndef PCK_PACKER_H #define PCK_PACKER_H -#include "core/reference.h" +#include "core/object/ref_counted.h" class FileAccess; -class PCKPacker : public Reference { - GDCLASS(PCKPacker, Reference); +class PCKPacker : public RefCounted { + GDCLASS(PCKPacker, RefCounted); FileAccess *file = nullptr; - int alignment; + int alignment = 0; uint64_t ofs = 0; Vector<uint8_t> key; @@ -50,9 +50,9 @@ class PCKPacker : public Reference { struct File { String path; String src_path; - uint64_t ofs; - uint64_t size; - bool encrypted; + uint64_t ofs = 0; + uint64_t size = 0; + bool encrypted = false; Vector<uint8_t> md5; }; Vector<File> files; diff --git a/core/io/resource.cpp b/core/io/resource.cpp new file mode 100644 index 0000000000..87b4d7195d --- /dev/null +++ b/core/io/resource.cpp @@ -0,0 +1,557 @@ +/*************************************************************************/ +/* resource.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 "resource.h" + +#include "core/core_string_names.h" +#include "core/io/file_access.h" +#include "core/io/resource_loader.h" +#include "core/math/math_funcs.h" +#include "core/object/script_language.h" +#include "core/os/os.h" +#include "scene/main/node.h" //only so casting works + +#include <stdio.h> + +void Resource::emit_changed() { + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void Resource::_resource_path_changed() { +} + +void Resource::set_path(const String &p_path, bool p_take_over) { + if (path_cache == p_path) { + return; + } + + if (path_cache != "") { + ResourceCache::lock.write_lock(); + ResourceCache::resources.erase(path_cache); + ResourceCache::lock.write_unlock(); + } + + path_cache = ""; + + ResourceCache::lock.read_lock(); + bool has_path = ResourceCache::resources.has(p_path); + ResourceCache::lock.read_unlock(); + + if (has_path) { + if (p_take_over) { + ResourceCache::lock.write_lock(); + Resource **res = ResourceCache::resources.getptr(p_path); + if (res) { + (*res)->set_name(""); + } + ResourceCache::lock.write_unlock(); + } else { + ResourceCache::lock.read_lock(); + bool exists = ResourceCache::resources.has(p_path); + ResourceCache::lock.read_unlock(); + + ERR_FAIL_COND_MSG(exists, "Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + } + } + path_cache = p_path; + + if (path_cache != "") { + ResourceCache::lock.write_lock(); + ResourceCache::resources[path_cache] = this; + ResourceCache::lock.write_unlock(); + } + + _resource_path_changed(); +} + +String Resource::get_path() const { + return path_cache; +} + +String Resource::generate_scene_unique_id() { + // Generate a unique enough hash, but still user-readable. + // If it's not unique it does not matter because the saver will try again. + OS::Date date = OS::get_singleton()->get_date(); + OS::Time time = OS::get_singleton()->get_time(); + uint32_t hash = hash_djb2_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_djb2_one_32(date.year, hash); + hash = hash_djb2_one_32(date.month, hash); + hash = hash_djb2_one_32(date.day, hash); + hash = hash_djb2_one_32(time.hour, hash); + hash = hash_djb2_one_32(time.minute, hash); + hash = hash_djb2_one_32(time.second, hash); + hash = hash_djb2_one_32(Math::rand(), hash); + + static constexpr uint32_t characters = 5; + static constexpr uint32_t char_count = ('z' - 'a'); + static constexpr uint32_t base = char_count + ('9' - '0'); + String id; + for (uint32_t i = 0; i < characters; i++) { + uint32_t c = hash % base; + if (c < char_count) { + id += String::chr('a' + c); + } else { + id += String::chr('0' + (c - char_count)); + } + hash /= base; + } + + return id; +} + +void Resource::set_scene_unique_id(const String &p_id) { + scene_unique_id = p_id; +} + +String Resource::get_scene_unique_id() const { + return scene_unique_id; +} + +void Resource::set_name(const String &p_name) { + name = p_name; +} + +String Resource::get_name() const { + return name; +} + +void Resource::update_configuration_warning() { + if (_update_configuration_warning) { + _update_configuration_warning(); + } +} + +bool Resource::editor_can_reload_from_file() { + return true; //by default yes +} + +void Resource::reset_state() { +} +Error Resource::copy_from(const Ref<Resource> &p_resource) { + ERR_FAIL_COND_V(p_resource.is_null(), ERR_INVALID_PARAMETER); + if (get_class() != p_resource->get_class()) { + return ERR_INVALID_PARAMETER; + } + + reset_state(); //may want to reset state + + List<PropertyInfo> pi; + p_resource->get_property_list(&pi); + + for (const PropertyInfo &E : pi) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + if (E.name == "resource_path") { + continue; //do not change path + } + + set(E.name, p_resource->get(E.name)); + } + return OK; +} +void Resource::reload_from_file() { + String path = get_path(); + if (!path.is_resource_file()) { + return; + } + + Ref<Resource> s = ResourceLoader::load(ResourceLoader::path_remap(path), get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + + if (!s.is_valid()) { + return; + } + + copy_from(s); +} + +Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache) { + List<PropertyInfo> plist; + get_property_list(&plist); + + Ref<Resource> r = Object::cast_to<Resource>(ClassDB::instantiate(get_class())); + ERR_FAIL_COND_V(r.is_null(), Ref<Resource>()); + + r->local_scene = p_for_scene; + + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + Variant p = get(E.name); + if (p.get_type() == Variant::OBJECT) { + RES sr = p; + if (sr.is_valid()) { + if (sr->is_local_to_scene()) { + if (remap_cache.has(sr)) { + p = remap_cache[sr]; + } else { + RES dupe = sr->duplicate_for_local_scene(p_for_scene, remap_cache); + p = dupe; + remap_cache[sr] = dupe; + } + } + } + } + + r->set(E.name, p); + } + + return r; +} + +void Resource::configure_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache) { + List<PropertyInfo> plist; + get_property_list(&plist); + + local_scene = p_for_scene; + + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + Variant p = get(E.name); + if (p.get_type() == Variant::OBJECT) { + RES sr = p; + if (sr.is_valid()) { + if (sr->is_local_to_scene()) { + if (!remap_cache.has(sr)) { + sr->configure_for_local_scene(p_for_scene, remap_cache); + remap_cache[sr] = sr; + } + } + } + } + } +} + +Ref<Resource> Resource::duplicate(bool p_subresources) const { + List<PropertyInfo> plist; + get_property_list(&plist); + + Ref<Resource> r = (Resource *)ClassDB::instantiate(get_class()); + ERR_FAIL_COND_V(r.is_null(), Ref<Resource>()); + + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + Variant p = get(E.name); + + if ((p.get_type() == Variant::DICTIONARY || p.get_type() == Variant::ARRAY)) { + r->set(E.name, p.duplicate(p_subresources)); + } else if (p.get_type() == Variant::OBJECT && (p_subresources || (E.usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE))) { + RES sr = p; + if (sr.is_valid()) { + r->set(E.name, sr->duplicate(p_subresources)); + } + } else { + r->set(E.name, p); + } + } + + return r; +} + +void Resource::_set_path(const String &p_path) { + set_path(p_path, false); +} + +void Resource::_take_over_path(const String &p_path) { + set_path(p_path, true); +} + +RID Resource::get_rid() const { + return RID(); +} + +void Resource::register_owner(Object *p_owner) { + owners.insert(p_owner->get_instance_id()); +} + +void Resource::unregister_owner(Object *p_owner) { + owners.erase(p_owner->get_instance_id()); +} + +void Resource::notify_change_to_owners() { + for (Set<ObjectID>::Element *E = owners.front(); E; E = E->next()) { + Object *obj = ObjectDB::get_instance(E->get()); + ERR_CONTINUE_MSG(!obj, "Object was deleted, while still owning a resource."); //wtf + //TODO store string + obj->call("resource_changed", RES(this)); + } +} + +#ifdef TOOLS_ENABLED + +uint32_t Resource::hash_edited_version() const { + uint32_t hash = hash_djb2_one_32(get_edited_version()); + + List<PropertyInfo> plist; + get_property_list(&plist); + + for (const PropertyInfo &E : plist) { + if (E.usage & PROPERTY_USAGE_STORAGE && E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) { + RES res = get(E.name); + if (res.is_valid()) { + hash = hash_djb2_one_32(res->hash_edited_version(), hash); + } + } + } + + return hash; +} + +#endif + +void Resource::set_local_to_scene(bool p_enable) { + local_to_scene = p_enable; +} + +bool Resource::is_local_to_scene() const { + return local_to_scene; +} + +Node *Resource::get_local_scene() const { + if (local_scene) { + return local_scene; + } + + if (_get_local_scene_func) { + return _get_local_scene_func(); + } + + return nullptr; +} + +void Resource::setup_local_to_scene() { + // Can't use GDVIRTUAL in Resource, so this will have to be done with a signal + emit_signal(SNAME("setup_local_to_scene_requested")); +} + +Node *(*Resource::_get_local_scene_func)() = nullptr; +void (*Resource::_update_configuration_warning)() = nullptr; + +void Resource::set_as_translation_remapped(bool p_remapped) { + if (remapped_list.in_list() == p_remapped) { + return; + } + + ResourceCache::lock.write_lock(); + + if (p_remapped) { + ResourceLoader::remapped_list.add(&remapped_list); + } else { + ResourceLoader::remapped_list.remove(&remapped_list); + } + + ResourceCache::lock.write_unlock(); +} + +bool Resource::is_translation_remapped() const { + return remapped_list.in_list(); +} + +#ifdef TOOLS_ENABLED +//helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored +void Resource::set_id_for_path(const String &p_path, const String &p_id) { + if (p_id == "") { + ResourceCache::path_cache_lock.write_lock(); + ResourceCache::resource_path_cache[p_path].erase(get_path()); + ResourceCache::path_cache_lock.write_unlock(); + } else { + ResourceCache::path_cache_lock.write_lock(); + ResourceCache::resource_path_cache[p_path][get_path()] = p_id; + ResourceCache::path_cache_lock.write_unlock(); + } +} + +String Resource::get_id_for_path(const String &p_path) const { + ResourceCache::path_cache_lock.read_lock(); + if (ResourceCache::resource_path_cache[p_path].has(get_path())) { + String result = ResourceCache::resource_path_cache[p_path][get_path()]; + ResourceCache::path_cache_lock.read_unlock(); + return result; + } else { + ResourceCache::path_cache_lock.read_unlock(); + return ""; + } +} +#endif + +void Resource::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path); + ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path); + ClassDB::bind_method(D_METHOD("get_path"), &Resource::get_path); + ClassDB::bind_method(D_METHOD("set_name", "name"), &Resource::set_name); + ClassDB::bind_method(D_METHOD("get_name"), &Resource::get_name); + ClassDB::bind_method(D_METHOD("get_rid"), &Resource::get_rid); + ClassDB::bind_method(D_METHOD("set_local_to_scene", "enable"), &Resource::set_local_to_scene); + ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene); + ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene); + ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene); + ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed); + + ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false)); + ADD_SIGNAL(MethodInfo("changed")); + ADD_SIGNAL(MethodInfo("setup_local_to_scene_requested")); + + ADD_GROUP("Resource", "resource_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resource_local_to_scene"), "set_local_to_scene", "is_local_to_scene"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_path", "get_path"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "resource_name"), "set_name", "get_name"); +} + +Resource::Resource() : + remapped_list(this) {} + +Resource::~Resource() { + if (path_cache != "") { + ResourceCache::lock.write_lock(); + ResourceCache::resources.erase(path_cache); + ResourceCache::lock.write_unlock(); + } + if (owners.size()) { + WARN_PRINT("Resource is still owned."); + } +} + +HashMap<String, Resource *> ResourceCache::resources; +#ifdef TOOLS_ENABLED +HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache; +#endif + +RWLock ResourceCache::lock; +#ifdef TOOLS_ENABLED +RWLock ResourceCache::path_cache_lock; +#endif + +void ResourceCache::clear() { + if (resources.size()) { + ERR_PRINT("Resources still in use at exit (run with --verbose for details)."); + if (OS::get_singleton()->is_stdout_verbose()) { + const String *K = nullptr; + while ((K = resources.next(K))) { + Resource *r = resources[*K]; + print_line(vformat("Resource still in use: %s (%s)", *K, r->get_class())); + } + } + } + + resources.clear(); +} + +void ResourceCache::reload_externals() { +} + +bool ResourceCache::has(const String &p_path) { + lock.read_lock(); + bool b = resources.has(p_path); + lock.read_unlock(); + + return b; +} + +Resource *ResourceCache::get(const String &p_path) { + lock.read_lock(); + + Resource **res = resources.getptr(p_path); + + lock.read_unlock(); + + if (!res) { + return nullptr; + } + + return *res; +} + +void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { + lock.read_lock(); + const String *K = nullptr; + while ((K = resources.next(K))) { + Resource *r = resources[*K]; + p_resources->push_back(Ref<Resource>(r)); + } + lock.read_unlock(); +} + +int ResourceCache::get_cached_resource_count() { + lock.read_lock(); + int rc = resources.size(); + lock.read_unlock(); + + return rc; +} + +void ResourceCache::dump(const char *p_file, bool p_short) { +#ifdef DEBUG_ENABLED + lock.read_lock(); + + Map<String, int> type_count; + + FileAccess *f = nullptr; + if (p_file) { + f = FileAccess::open(p_file, FileAccess::WRITE); + ERR_FAIL_COND_MSG(!f, "Cannot create file at path '" + String(p_file) + "'."); + } + + const String *K = nullptr; + while ((K = resources.next(K))) { + Resource *r = resources[*K]; + + if (!type_count.has(r->get_class())) { + type_count[r->get_class()] = 0; + } + + type_count[r->get_class()]++; + + if (!p_short) { + if (f) { + f->store_line(r->get_class() + ": " + r->get_path()); + } + } + } + + for (Map<String, int>::Element *E = type_count.front(); E; E = E->next()) { + if (f) { + f->store_line(E->key() + " count: " + itos(E->get())); + } + } + if (f) { + f->close(); + memdelete(f); + } + + lock.read_unlock(); +#else + WARN_PRINT("ResourceCache::dump only with in debug builds."); +#endif +} diff --git a/core/io/resource.h b/core/io/resource.h new file mode 100644 index 0000000000..9ccc247887 --- /dev/null +++ b/core/io/resource.h @@ -0,0 +1,175 @@ +/*************************************************************************/ +/* resource.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 RESOURCE_H +#define RESOURCE_H + +#include "core/io/resource_uid.h" +#include "core/object/class_db.h" +#include "core/object/ref_counted.h" +#include "core/templates/safe_refcount.h" +#include "core/templates/self_list.h" + +#define RES_BASE_EXTENSION(m_ext) \ +public: \ + static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension(m_ext, get_class_static()); } \ + virtual String get_base_extension() const override { return m_ext; } \ + \ +private: + +class Resource : public RefCounted { + GDCLASS(Resource, RefCounted); + OBJ_CATEGORY("Resources"); + +public: + static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension("res", get_class_static()); } + virtual String get_base_extension() const { return "res"; } + +private: + Set<ObjectID> owners; + + friend class ResBase; + friend class ResourceCache; + + String name; + String path_cache; + String scene_unique_id; + +#ifdef TOOLS_ENABLED + uint64_t last_modified_time = 0; + uint64_t import_last_modified_time = 0; + String import_path; +#endif + + bool local_to_scene = false; + friend class SceneState; + Node *local_scene = nullptr; + + SelfList<Resource> remapped_list; + +protected: + void emit_changed(); + + void notify_change_to_owners(); + + virtual void _resource_path_changed(); + static void _bind_methods(); + + void _set_path(const String &p_path); + void _take_over_path(const String &p_path); + +public: + static Node *(*_get_local_scene_func)(); //used by editor + static void (*_update_configuration_warning)(); //used by editor + + void update_configuration_warning(); + virtual bool editor_can_reload_from_file(); + virtual void reset_state(); //for resources that use variable amount of properties, either via _validate_property or _get_property_list, this function needs to be implemented to correctly clear state + virtual Error copy_from(const Ref<Resource> &p_resource); + virtual void reload_from_file(); + + void register_owner(Object *p_owner); + void unregister_owner(Object *p_owner); + + void set_name(const String &p_name); + String get_name() const; + + virtual void set_path(const String &p_path, bool p_take_over = false); + String get_path() const; + + static String generate_scene_unique_id(); + void set_scene_unique_id(const String &p_id); + String get_scene_unique_id() const; + + virtual Ref<Resource> duplicate(bool p_subresources = false) const; + Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache); + void configure_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource>> &remap_cache); + + void set_local_to_scene(bool p_enable); + bool is_local_to_scene() const; + virtual void setup_local_to_scene(); + + Node *get_local_scene() const; + +#ifdef TOOLS_ENABLED + + uint32_t hash_edited_version() const; + + virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; } + uint64_t get_last_modified_time() const { return last_modified_time; } + + virtual void set_import_last_modified_time(uint64_t p_time) { import_last_modified_time = p_time; } + uint64_t get_import_last_modified_time() const { return import_last_modified_time; } + + void set_import_path(const String &p_path) { import_path = p_path; } + String get_import_path() const { return import_path; } + +#endif + + void set_as_translation_remapped(bool p_remapped); + bool is_translation_remapped() const; + + virtual RID get_rid() const; // some resources may offer conversion to RID + +#ifdef TOOLS_ENABLED + //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored + void set_id_for_path(const String &p_path, const String &p_id); + String get_id_for_path(const String &p_path) const; +#endif + + Resource(); + ~Resource(); +}; + +typedef Ref<Resource> RES; + +class ResourceCache { + friend class Resource; + friend class ResourceLoader; //need the lock + static RWLock lock; + static HashMap<String, Resource *> resources; +#ifdef TOOLS_ENABLED + static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs. + static RWLock path_cache_lock; +#endif // TOOLS_ENABLED + friend void unregister_core_types(); + static void clear(); + friend void register_core_types(); + +public: + static void reload_externals(); + static bool has(const String &p_path); + static Resource *get(const String &p_path); + static void dump(const char *p_file = nullptr, bool p_short = false); + static void get_cached_resources(List<Ref<Resource>> *p_resources); + static int get_cached_resource_count(); +}; + +#endif // RESOURCE_H diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 5097f6d98b..00d4d093da 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,18 +30,17 @@ #include "resource_format_binary.h" -#include "core/image.h" +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" #include "core/io/file_access_compressed.h" +#include "core/io/image.h" #include "core/io/marshalls.h" -#include "core/os/dir_access.h" -#include "core/project_settings.h" #include "core/version.h" //#define print_bl(m_what) print_line(m_what) #define print_bl(m_what) (void)(m_what) enum { - //numbering must be different from variant, in case new variant types are added (variant must be always contiguous for jumptable optimization) VARIANT_NIL = 1, VARIANT_BOOL = 2, @@ -52,7 +51,7 @@ enum { VARIANT_RECT2 = 11, VARIANT_VECTOR3 = 12, VARIANT_PLANE = 13, - VARIANT_QUAT = 14, + VARIANT_QUATERNION = 14, VARIANT_AABB = 15, VARIANT_MATRIX3 = 16, VARIANT_TRANSFORM = 17, @@ -85,12 +84,12 @@ enum { OBJECT_EXTERNAL_RESOURCE = 1, OBJECT_INTERNAL_RESOURCE = 2, OBJECT_EXTERNAL_RESOURCE_INDEX = 3, - //version 2: added 64 bits support for float and int - //version 3: changed nodepath encoding - FORMAT_VERSION = 3, + // Version 2: added 64 bits support for float and int. + // Version 3: changed nodepath encoding. + // Version 4: new string ID for ext/subresources, breaks forward compat. + FORMAT_VERSION = 4, FORMAT_VERSION_CAN_RENAME_DEPS = 1, FORMAT_VERSION_NO_NODEPATH_PROPERTY = 3, - }; void ResourceLoaderBinary::_advance_padding(uint32_t p_len) { @@ -201,8 +200,8 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { v.d = f->get_real(); r_v = v; } break; - case VARIANT_QUAT: { - Quat v; + case VARIANT_QUATERNION: { + Quaternion v; v.x = f->get_real(); v.y = f->get_real(); v.z = f->get_real(); @@ -247,7 +246,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } break; case VARIANT_TRANSFORM: { - Transform v; + Transform3D v; v.basis.elements[0].x = f->get_real(); v.basis.elements[0].y = f->get_real(); v.basis.elements[0].z = f->get_real(); @@ -263,11 +262,11 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { r_v = v; } break; case VARIANT_COLOR: { - Color v; - v.r = f->get_real(); - v.g = f->get_real(); - v.b = f->get_real(); - v.a = f->get_real(); + Color v; // Colors should always be in single-precision. + v.r = f->get_float(); + v.g = f->get_float(); + v.b = f->get_float(); + v.a = f->get_float(); r_v = v; } break; @@ -313,21 +312,22 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { } break; case OBJECT_INTERNAL_RESOURCE: { uint32_t index = f->get_32(); - String path = res_path + "::" + itos(index); + String path; - if (use_nocache) { - if (!internal_index_cache.has(path)) { - WARN_PRINT(String("Couldn't load resource (no cache): " + path).utf8().get_data()); - } - r_v = internal_index_cache[path]; + if (using_named_scene_ids) { // New format. + ERR_FAIL_INDEX_V((int)index, internal_resources.size(), ERR_PARSE_ERROR); + path = internal_resources[index].path; } else { - RES res = ResourceLoader::load(path); - if (res.is_null()) { - WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data()); - } - r_v = res; + path += res_path + "::" + itos(index); } + //always use internal cache for loading internal resources + if (!internal_index_cache.has(path)) { + WARN_PRINT(String("Couldn't load resource (no cache): " + path).utf8().get_data()); + r_v = Variant(); + } else { + r_v = internal_index_cache[path]; + } } break; case OBJECT_EXTERNAL_RESOURCE: { //old file format, still around for compatibility @@ -385,7 +385,6 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V(ERR_FILE_CORRUPT); } break; } - } break; case VARIANT_CALLABLE: { r_v = Callable(); @@ -647,7 +646,7 @@ Error ResourceLoaderBinary::load() { } } else { - Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, local_path); + Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); if (err != OK) { if (!ResourceLoader::get_abort_on_missing_resources()) { ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); @@ -666,18 +665,20 @@ Error ResourceLoaderBinary::load() { //maybe it is loaded already String path; - int subindex = 0; + String id; if (!main) { path = internal_resources[i].path; if (path.begins_with("local://")) { path = path.replace_first("local://", ""); - subindex = path.to_int(); + id = path; path = res_path + "::" + path; + + internal_resources.write[i].path = path; // Update path. } - if (!use_nocache) { + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) { if (ResourceCache::has(path)) { //already loaded, don't do anything stage++; @@ -686,7 +687,7 @@ Error ResourceLoaderBinary::load() { } } } else { - if (!use_nocache && !ResourceCache::has(res_path)) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && !ResourceCache::has(res_path)) { path = res_path; } } @@ -697,26 +698,40 @@ Error ResourceLoaderBinary::load() { String t = get_unicode_string(); - Object *obj = ClassDB::instance(t); - if (!obj) { - error = ERR_FILE_CORRUPT; - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + "."); - } + RES res; - Resource *r = Object::cast_to<Resource>(obj); - if (!r) { - String obj_class = obj->get_class(); - error = ERR_FILE_CORRUPT; - memdelete(obj); //bye - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + "."); + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { + //use the existing one + Resource *r = ResourceCache::get(path); + if (r->get_class() == t) { + r->reset_state(); + res = Ref<Resource>(r); + } } - RES res = RES(r); + if (res.is_null()) { + //did not replace + + Object *obj = ClassDB::instantiate(t); + if (!obj) { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + "."); + } + + Resource *r = Object::cast_to<Resource>(obj); + if (!r) { + String obj_class = obj->get_class(); + error = ERR_FILE_CORRUPT; + memdelete(obj); //bye + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + "."); + } - if (path != String()) { - r->set_path(path); + res = RES(r); + if (path != String() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it + } + r->set_scene_unique_id(id); } - r->set_subindex(subindex); if (!main) { internal_index_cache[path] = res; @@ -801,13 +816,18 @@ String ResourceLoaderBinary::get_unicode_string() { } void ResourceLoaderBinary::get_dependencies(FileAccess *p_f, List<String> *p_dependencies, bool p_add_types) { - open(p_f); + open(p_f, false, true); if (error) { return; } for (int i = 0; i < external_resources.size(); i++) { - String dep = external_resources[i].path; + String dep; + if (external_resources[i].uid != ResourceUID::INVALID_ID) { + dep = ResourceUID::get_singleton()->id_to_text(external_resources[i].uid); + } else { + dep = external_resources[i].path; + } if (p_add_types && external_resources[i].type != String()) { dep += "::" + external_resources[i].type; @@ -817,7 +837,7 @@ void ResourceLoaderBinary::get_dependencies(FileAccess *p_f, List<String> *p_dep } } -void ResourceLoaderBinary::open(FileAccess *p_f) { +void ResourceLoaderBinary::open(FileAccess *p_f, bool p_no_resources, bool p_keep_uuid_paths) { error = OK; f = p_f; @@ -844,7 +864,7 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { bool big_endian = f->get_32(); bool use_real64 = f->get_32(); - f->set_endian_swap(big_endian != 0); //read big endian if saved as big endian + f->set_big_endian(big_endian != 0); //read big endian if saved as big endian uint32_t ver_major = f->get_32(); uint32_t ver_minor = f->get_32(); @@ -863,7 +883,8 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { if (ver_format > FORMAT_VERSION || ver_major > VERSION_MAJOR) { f->close(); - ERR_FAIL_MSG("File format '" + itos(FORMAT_VERSION) + "." + itos(ver_major) + "." + itos(ver_minor) + "' is too new! Please upgrade to a new engine version: " + local_path + "."); + ERR_FAIL_MSG(vformat("File '%s' can't be loaded, as it uses a format version (%d) or engine version (%d.%d) which are not supported by your engine version (%s).", + local_path, ver_format, ver_major, ver_minor, VERSION_BRANCH)); } type = get_unicode_string(); @@ -871,10 +892,29 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { print_bl("type: " + type); importmd_ofs = f->get_64(); - for (int i = 0; i < 14; i++) { + uint32_t flags = f->get_32(); + if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_NAMED_SCENE_IDS) { + using_named_scene_ids = true; + } + if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_UIDS) { + using_uids = true; + } + + if (using_uids) { + uid = f->get_64(); + } else { + f->get_64(); // skip over uid field + uid = ResourceUID::INVALID_ID; + } + + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->get_32(); //skip a few reserved fields } + if (p_no_resources) { + return; + } + uint32_t string_table_size = f->get_32(); string_map.resize(string_table_size); for (uint32_t i = 0; i < string_table_size; i++) { @@ -888,8 +928,18 @@ void ResourceLoaderBinary::open(FileAccess *p_f) { for (uint32_t i = 0; i < ext_resources_size; i++) { ExtResource er; er.type = get_unicode_string(); - er.path = get_unicode_string(); + if (using_uids) { + er.uid = f->get_64(); + if (!p_keep_uuid_paths && er.uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(er.uid)) { + // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path. + er.path = ResourceUID::get_singleton()->get_id_path(er.uid); + } else { + WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UUID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data()); + } + } + } external_resources.push_back(er); } @@ -940,7 +990,7 @@ String ResourceLoaderBinary::recognize(FileAccess *p_f) { bool big_endian = f->get_32(); f->get_32(); // use_real64 - f->set_endian_swap(big_endian != 0); //read big endian if saved as big endian + f->set_big_endian(big_endian != 0); //read big endian if saved as big endian uint32_t ver_major = f->get_32(); f->get_32(); // ver_minor @@ -962,7 +1012,7 @@ ResourceLoaderBinary::~ResourceLoaderBinary() { } } -RES ResourceFormatLoaderBinary::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderBinary::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -973,7 +1023,7 @@ RES ResourceFormatLoaderBinary::load(const String &p_path, const String &p_origi ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot open file '" + p_path + "'."); ResourceLoaderBinary loader; - loader.use_nocache = p_no_cache; + loader.cache_mode = p_cache_mode; loader.use_sub_threads = p_use_sub_threads; loader.progress = r_progress; String path = p_original_path != "" ? p_original_path : p_path; @@ -1005,8 +1055,8 @@ void ResourceFormatLoaderBinary::get_recognized_extensions_for_type(const String extensions.sort(); - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - String ext = E->get().to_lower(); + for (const String &E : extensions) { + String ext = E.to_lower(); p_extensions->push_back(ext); } } @@ -1016,8 +1066,8 @@ void ResourceFormatLoaderBinary::get_recognized_extensions(List<String> *p_exten ClassDB::get_resource_base_extensions(&extensions); extensions.sort(); - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - String ext = E->get().to_lower(); + for (const String &E : extensions) { + String ext = E.to_lower(); p_extensions->push_back(ext); } } @@ -1089,13 +1139,13 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons bool big_endian = f->get_32(); bool use_real64 = f->get_32(); - f->set_endian_swap(big_endian != 0); //read big endian if saved as big endian + f->set_big_endian(big_endian != 0); //read big endian if saved as big endian #ifdef BIG_ENDIAN_ENABLED fw->store_32(!big_endian); #else fw->store_32(big_endian); #endif - fw->set_endian_swap(big_endian != 0); + fw->set_big_endian(big_endian != 0); fw->store_32(use_real64); //use real64 uint32_t ver_major = f->get_32(); @@ -1136,7 +1186,9 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons if (ver_format > FORMAT_VERSION || ver_major > VERSION_MAJOR) { memdelete(f); memdelete(fw); - ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "File format '" + itos(FORMAT_VERSION) + "." + itos(ver_major) + "." + itos(ver_minor) + "' is too new! Please upgrade to a new engine version: " + local_path + "."); + ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, + vformat("File '%s' can't be loaded, as it uses a format version (%d) or engine version (%d.%d) which are not supported by your engine version (%s).", + local_path, ver_format, ver_major, ver_minor, VERSION_BRANCH)); } // Since we're not actually converting the file contents, leave the version @@ -1147,12 +1199,19 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons save_ustring(fw, get_ustring(f)); //type - size_t md_ofs = f->get_position(); - size_t importmd_ofs = f->get_64(); + uint64_t md_ofs = f->get_position(); + uint64_t importmd_ofs = f->get_64(); fw->store_64(0); //metadata offset - for (int i = 0; i < 14; i++) { - fw->store_32(0); + uint32_t flags = f->get_32(); + bool using_uids = (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_UIDS); + uint64_t uid_data = f->get_64(); + + fw->store_32(flags); + fw->store_64(uid_data); + + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { + fw->store_32(0); // reserved f->get_32(); } @@ -1173,6 +1232,16 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons String type = get_ustring(f); String path = get_ustring(f); + if (using_uids) { + ResourceUID::ID uid = f->get_64(); + if (uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(uid)) { + // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path. + path = ResourceUID::get_singleton()->get_id_path(uid); + } + } + } + bool relative = false; if (!path.begins_with("res://")) { path = local_path.plus_file(path).simplify_path(); @@ -1184,6 +1253,8 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons path = np; } + String full_path = path; + if (relative) { //restore relative path = local_path.path_to_file(path); @@ -1191,6 +1262,11 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons save_ustring(fw, type); save_ustring(fw, path); + + if (using_uids) { + ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(full_path); + fw->store_64(uid); + } } int64_t size_diff = (int64_t)fw->get_position() - (int64_t)f->get_position(); @@ -1235,7 +1311,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons String ResourceFormatLoaderBinary::get_resource_type(const String &p_path) const { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { - return ""; //could not rwead + return ""; //could not read } ResourceLoaderBinary loader; @@ -1246,6 +1322,28 @@ String ResourceFormatLoaderBinary::get_resource_type(const String &p_path) const return ClassDB::get_compatibility_remapped_class(r); } +ResourceUID::ID ResourceFormatLoaderBinary::get_resource_uid(const String &p_path) const { + String ext = p_path.get_extension().to_lower(); + if (!ClassDB::is_resource_extension(ext)) { + return ResourceUID::INVALID_ID; + } + + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + if (!f) { + return ResourceUID::INVALID_ID; //could not read + } + + ResourceLoaderBinary loader; + loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path); + loader.res_path = loader.local_path; + //loader.set_local_path( Globals::get_singleton()->localize_path(p_path) ); + loader.open(f, true); + if (loader.error != OK) { + return ResourceUID::INVALID_ID; //could not read + } + return loader.uid; +} + /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// @@ -1259,11 +1357,7 @@ void ResourceFormatSaverBinaryInstance::_pad_buffer(FileAccess *f, int p_bytes) } } -void ResourceFormatSaverBinaryInstance::_write_variant(const Variant &p_property, const PropertyInfo &p_hint) { - write_variant(f, p_property, resource_set, external_resources, string_map, p_hint); -} - -void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { +void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint) { switch (p_property.get_type()) { case Variant::NIL: { f->store_32(VARIANT_NIL); @@ -1361,9 +1455,9 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia f->store_real(val.d); } break; - case Variant::QUAT: { - f->store_32(VARIANT_QUAT); - Quat val = p_property; + case Variant::QUATERNION: { + f->store_32(VARIANT_QUATERNION); + Quaternion val = p_property; f->store_real(val.x); f->store_real(val.y); f->store_real(val.z); @@ -1406,9 +1500,9 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia f->store_real(val.elements[2].z); } break; - case Variant::TRANSFORM: { + case Variant::TRANSFORM3D: { f->store_32(VARIANT_TRANSFORM); - Transform val = p_property; + Transform3D val = p_property; f->store_real(val.basis.elements[0].x); f->store_real(val.basis.elements[0].y); f->store_real(val.basis.elements[0].z); @@ -1464,7 +1558,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia } } break; - case Variant::_RID: { + case Variant::RID: { f->store_32(VARIANT_RID); WARN_PRINT("Can't save RIDs."); RID val = p_property; @@ -1482,13 +1576,13 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia f->store_32(OBJECT_EXTERNAL_RESOURCE_INDEX); f->store_32(external_resources[res]); } else { - if (!resource_set.has(res)) { + if (!resource_map.has(res)) { f->store_32(OBJECT_EMPTY); ERR_FAIL_MSG("Resource was not pre cached for the resource section, most likely due to circular reference."); } f->store_32(OBJECT_INTERNAL_RESOURCE); - f->store_32(res->get_subindex()); + f->store_32(resource_map[res]); //internal resource } @@ -1510,14 +1604,14 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia List<Variant> keys; d.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + for (const Variant &E : keys) { /* - if (!_check_type(dict[E->get()])) + if (!_check_type(dict[E])) continue; */ - write_variant(f, E->get(), resource_set, external_resources, string_map); - write_variant(f, d[E->get()], resource_set, external_resources, string_map); + write_variant(f, E, resource_map, external_resources, string_map); + write_variant(f, d[E], resource_map, external_resources, string_map); } } break; @@ -1526,7 +1620,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(FileAccess *f, const Varia Array a = p_property; f->store_32(uint32_t(a.size())); for (int i = 0; i < a.size(); i++) { - write_variant(f, a[i], resource_set, external_resources, string_map); + write_variant(f, a[i], resource_map, external_resources, string_map); } } break; @@ -1667,15 +1761,15 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant res->get_property_list(&property_list); - for (List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { - if (E->get().usage & PROPERTY_USAGE_STORAGE) { - Variant value = res->get(E->get().name); - if (E->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + for (const PropertyInfo &E : property_list) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + Variant value = res->get(E.name); + if (E.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { RES sres = value; if (sres.is_valid()) { NonPersistentKey npk; npk.base = res; - npk.property = E->get().name; + npk.property = E.name; non_persistent_map[npk] = sres; resource_set.insert(sres); saved_resources.push_back(sres); @@ -1705,9 +1799,9 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant Dictionary d = p_variant; List<Variant> keys; d.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - _find_resources(E->get()); - Variant v = d[E->get()]; + for (const Variant &E : keys) { + _find_resources(E); + Variant v = d[E]; _find_resources(v); } } break; @@ -1788,7 +1882,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p if (big_endian) { f->store_32(1); - f->set_endian_swap(true); + f->set_big_endian(true); } else { f->store_32(0); } @@ -1806,46 +1900,49 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p save_unicode_string(f, p_resource->get_class()); f->store_64(0); //offset to import metadata - for (int i = 0; i < 14; i++) { + f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS); + ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true); + f->store_64(uid); + for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) { f->store_32(0); // reserved } List<ResourceData> resources; { - for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { + for (const RES &E : saved_resources) { ResourceData &rd = resources.push_back(ResourceData())->get(); - rd.type = E->get()->get_class(); + rd.type = E->get_class(); List<PropertyInfo> property_list; - E->get()->get_property_list(&property_list); + E->get_property_list(&property_list); - for (List<PropertyInfo>::Element *F = property_list.front(); F; F = F->next()) { - if (skip_editor && F->get().name.begins_with("__editor")) { + for (const PropertyInfo &F : property_list) { + if (skip_editor && F.name.begins_with("__editor")) { continue; } - if ((F->get().usage & PROPERTY_USAGE_STORAGE)) { + if ((F.usage & PROPERTY_USAGE_STORAGE)) { Property p; - p.name_idx = get_string_index(F->get().name); + p.name_idx = get_string_index(F.name); - if (F->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + if (F.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { NonPersistentKey npk; - npk.base = E->get(); - npk.property = F->get().name; + npk.base = E; + npk.property = F.name; if (non_persistent_map.has(npk)) { p.value = non_persistent_map[npk]; } } else { - p.value = E->get()->get(F->get().name); + p.value = E->get(F.name); } - Variant default_value = ClassDB::class_get_default_property_value(E->get()->get_class(), F->get().name); + Variant default_value = ClassDB::class_get_default_property_value(E->get_class(), F.name); if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, p.value, default_value))) { continue; } - p.pi = F->get(); + p.pi = F; rd.properties.push_back(p); } @@ -1872,41 +1969,47 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p String path = save_order[i]->get_path(); path = relative_paths ? local_path.path_to_file(path) : path; save_unicode_string(f, path); + ResourceUID::ID ruid = ResourceSaver::get_resource_id_for_path(save_order[i]->get_path(), false); + f->store_64(ruid); } // save internal resource table f->store_32(saved_resources.size()); //amount of internal resources Vector<uint64_t> ofs_pos; - Set<int> used_indices; + Set<String> used_unique_ids; - for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { - RES r = E->get(); + for (RES &r : saved_resources) { if (r->get_path() == "" || r->get_path().find("::") != -1) { - if (r->get_subindex() != 0) { - if (used_indices.has(r->get_subindex())) { - r->set_subindex(0); //repeated + if (r->get_scene_unique_id() != "") { + if (used_unique_ids.has(r->get_scene_unique_id())) { + r->set_scene_unique_id(""); } else { - used_indices.insert(r->get_subindex()); + used_unique_ids.insert(r->get_scene_unique_id()); } } } } - for (List<RES>::Element *E = saved_resources.front(); E; E = E->next()) { - RES r = E->get(); + 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->get_subindex() == 0) { - int new_subindex = 1; - if (used_indices.size()) { - new_subindex = used_indices.back()->get() + 1; + if (r->get_scene_unique_id() == "") { + String new_id; + + while (true) { + new_id = r->get_class() + "_" + Resource::generate_scene_unique_id(); + if (!used_unique_ids.has(new_id)) { + break; + } } - r->set_subindex(new_subindex); - used_indices.insert(new_subindex); + r->set_scene_unique_id(new_id); + used_unique_ids.insert(new_id); } - save_unicode_string(f, "local://" + itos(r->get_subindex())); + save_unicode_string(f, "local://" + r->get_scene_unique_id()); if (takeover_paths) { - r->set_path(p_path + "::" + itos(r->get_subindex()), true); + r->set_path(p_path + "::" + r->get_scene_unique_id(), true); } #ifdef TOOLS_ENABLED r->set_edited(false); @@ -1916,22 +2019,20 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p } ofs_pos.push_back(f->get_position()); f->store_64(0); //offset in 64 bits + resource_map[r] = res_index++; } Vector<uint64_t> ofs_table; //now actually save the resources - for (List<ResourceData>::Element *E = resources.front(); E; E = E->next()) { - ResourceData &rd = E->get(); - + for (const ResourceData &rd : resources) { ofs_table.push_back(f->get_position()); save_unicode_string(f, rd.type); f->store_32(rd.properties.size()); - for (List<Property>::Element *F = rd.properties.front(); F; F = F->next()) { - Property &p = F->get(); + for (const Property &p : rd.properties) { f->store_32(p.name_idx); - _write_variant(p.value, F->get().pi); + write_variant(f, p.value, resource_map, external_resources, string_map, p.pi); } } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index 54cddca49e..a6e6d1848e 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,9 @@ #ifndef RESOURCE_FORMAT_BINARY_H #define RESOURCE_FORMAT_BINARY_H +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" -#include "core/os/file_access.h" class ResourceLoaderBinary { bool translation_remapped = false; @@ -47,6 +47,8 @@ class ResourceLoaderBinary { uint64_t importmd_ofs = 0; + ResourceUID::ID uid = ResourceUID::INVALID_ID; + Vector<char> str_buf; List<RES> resource_cache; @@ -57,9 +59,12 @@ class ResourceLoaderBinary { struct ExtResource { String path; String type; + ResourceUID::ID uid = ResourceUID::INVALID_ID; RES cache; }; + bool using_named_scene_ids = false; + bool using_uids = false; bool use_sub_threads = false; float *progress = nullptr; Vector<ExtResource> external_resources; @@ -78,7 +83,7 @@ class ResourceLoaderBinary { Map<String, String> remaps; Error error = OK; - bool use_nocache = false; + ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; friend class ResourceFormatLoaderBinary; @@ -93,7 +98,7 @@ public: void set_translation_remapped(bool p_remapped); void set_remaps(const Map<String, String> &p_remaps) { remaps = p_remaps; } - void open(FileAccess *p_f); + void open(FileAccess *p_f, bool p_no_resources = false, bool p_keep_uuid_paths = false); String recognize(FileAccess *p_f); void get_dependencies(FileAccess *p_f, List<String> *p_dependencies, bool p_add_types); @@ -103,11 +108,12 @@ public: class ResourceFormatLoaderBinary : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const; virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); virtual Error rename_dependencies(const String &p_path, const Map<String, String> &p_map); }; @@ -150,14 +156,19 @@ class ResourceFormatSaverBinaryInstance { }; static void _pad_buffer(FileAccess *f, int p_bytes); - void _write_variant(const Variant &p_property, const PropertyInfo &p_hint = PropertyInfo()); void _find_resources(const Variant &p_variant, bool p_main = false); static void save_unicode_string(FileAccess *f, const String &p_string, bool p_bit_on_len = false); int get_string_index(const String &p_string); public: + enum { + FORMAT_FLAG_NAMED_SCENE_IDS = 1, + FORMAT_FLAG_UIDS = 2, + // Amount of reserved 32-bit fields in resource header + RESERVED_FIELDS = 11 + }; Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); - static void write_variant(FileAccess *f, const Variant &p_property, Set<RES> &resource_set, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); + static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo()); }; class ResourceFormatSaverBinary : public ResourceFormatSaver { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index 4d980bcf1a..1e166015b0 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,9 @@ #include "resource_importer.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" -#include "core/variant_parser.h" +#include "core/variant/variant_parser.h" bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const { return p_a->get_importer_name() < p_b->get_importer_name(); @@ -93,6 +93,8 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy r_path_and_type.type = ClassDB::get_compatibility_remapped_class(value); } else if (assign == "importer") { r_path_and_type.importer = value; + } else if (assign == "uid") { + r_path_and_type.uid = ResourceUID::get_singleton()->text_to_id(value); } else if (assign == "group_file") { r_path_and_type.group_file = value; } else if (assign == "metadata") { @@ -116,7 +118,7 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy return OK; } -RES ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { PathAndType pat; Error err = _get_path_and_type(p_path, pat); @@ -128,7 +130,7 @@ RES ResourceFormatImporter::load(const String &p_path, const String &p_original_ return RES(); } - RES res = ResourceLoader::_load(pat.path, p_path, pat.type, p_no_cache, r_error, p_use_sub_threads, r_progress); + RES res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress); #ifdef TOOLS_ENABLED if (res.is_valid()) { @@ -146,10 +148,10 @@ void ResourceFormatImporter::get_recognized_extensions(List<String> *p_extension for (int i = 0; i < importers.size(); i++) { List<String> local_exts; importers[i]->get_recognized_extensions(&local_exts); - for (List<String>::Element *F = local_exts.front(); F; F = F->next()) { - if (!found.has(F->get())) { - p_extensions->push_back(F->get()); - found.insert(F->get()); + for (const String &F : local_exts) { + if (!found.has(F)) { + p_extensions->push_back(F); + found.insert(F); } } } @@ -175,10 +177,10 @@ void ResourceFormatImporter::get_recognized_extensions_for_type(const String &p_ List<String> local_exts; importers[i]->get_recognized_extensions(&local_exts); - for (List<String>::Element *F = local_exts.front(); F; F = F->next()) { - if (!found.has(F->get())) { - p_extensions->push_back(F->get()); - found.insert(F->get()); + for (const String &F : local_exts) { + if (!found.has(F)) { + p_extensions->push_back(F); + found.insert(F); } } } @@ -192,8 +194,32 @@ bool ResourceFormatImporter::recognize_path(const String &p_path, const String & return FileAccess::exists(p_path + ".import"); } -bool ResourceFormatImporter::can_be_imported(const String &p_path) const { - return ResourceFormatLoader::recognize_path(p_path); +Error ResourceFormatImporter::get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const { + r_order = 0; + r_importer = ""; + + r_can_threads = false; + Ref<ResourceImporter> importer; + + if (FileAccess::exists(p_path + ".import")) { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err == OK) { + importer = get_importer_by_name(pat.importer); + } + } else { + importer = get_importer_by_extension(p_path.get_extension().to_lower()); + } + + if (importer.is_valid()) { + r_order = importer->get_import_order(); + r_importer = importer->get_importer_name(); + r_can_threads = importer->can_import_threaded(); + return OK; + } else { + return ERR_INVALID_PARAMETER; + } } int ResourceFormatImporter::get_import_order(const String &p_path) const { @@ -312,6 +338,17 @@ String ResourceFormatImporter::get_resource_type(const String &p_path) const { return pat.type; } +ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) const { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err != OK) { + return ResourceUID::INVALID_ID; + } + + return pat.uid; +} + Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) const { PathAndType pat; Error err = _get_path_and_type(p_path, pat); @@ -348,14 +385,20 @@ void ResourceFormatImporter::get_importers_for_extension(const String &p_extensi for (int i = 0; i < importers.size(); i++) { List<String> local_exts; importers[i]->get_recognized_extensions(&local_exts); - for (List<String>::Element *F = local_exts.front(); F; F = F->next()) { - if (p_extension.to_lower() == F->get()) { + for (const String &F : local_exts) { + if (p_extension.to_lower() == F) { r_importers->push_back(importers[i]); } } } } +void ResourceFormatImporter::get_importers(List<Ref<ResourceImporter>> *r_importers) { + for (int i = 0; i < importers.size(); i++) { + r_importers->push_back(importers[i]); + } +} + Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const String &p_extension) const { Ref<ResourceImporter> importer; float priority = 0; @@ -363,8 +406,8 @@ Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const St for (int i = 0; i < importers.size(); i++) { List<String> local_exts; importers[i]->get_recognized_extensions(&local_exts); - for (List<String>::Element *F = local_exts.front(); F; F = F->next()) { - if (p_extension.to_lower() == F->get() && importers[i]->get_priority() > priority) { + for (const String &F : local_exts) { + if (p_extension.to_lower() == F && importers[i]->get_priority() > priority) { importer = importers[i]; priority = importers[i]->get_priority(); } @@ -415,3 +458,8 @@ ResourceFormatImporter *ResourceFormatImporter::singleton = nullptr; ResourceFormatImporter::ResourceFormatImporter() { singleton = this; } + +void ResourceImporter::_bind_methods() { + BIND_ENUM_CONSTANT(IMPORT_ORDER_DEFAULT); + BIND_ENUM_CONSTANT(IMPORT_ORDER_SCENE); +} diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index d31a9a0194..a1cacbd306 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,6 +42,7 @@ class ResourceFormatImporter : public ResourceFormatLoader { String importer; String group_file; Variant metadata; + uint64_t uid = ResourceUID::INVALID_ID; }; Error _get_path_and_type(const String &p_path, PathAndType &r_path_and_type, bool *r_valid = nullptr) const; @@ -57,12 +58,14 @@ class ResourceFormatImporter : public ResourceFormatLoader { public: static ResourceFormatImporter *get_singleton() { return singleton; } - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const; + virtual Variant get_resource_metadata(const String &p_path) const; virtual bool is_import_valid(const String &p_path) const; virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); @@ -70,9 +73,10 @@ public: virtual String get_import_group_file(const String &p_path) const; virtual bool exists(const String &p_path) const; - virtual bool can_be_imported(const String &p_path) const; virtual int get_import_order(const String &p_path) const; + Error get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const; + String get_internal_resource_path(const String &p_path) const; void get_internal_resource_path_list(const String &p_path, List<String> *r_paths); @@ -83,6 +87,7 @@ public: Ref<ResourceImporter> get_importer_by_name(const String &p_name) const; Ref<ResourceImporter> get_importer_by_extension(const String &p_extension) const; void get_importers_for_extension(const String &p_extension, List<Ref<ResourceImporter>> *r_importers); + void get_importers(List<Ref<ResourceImporter>> *r_importers); bool are_import_settings_valid(const String &p_path) const; String get_import_settings_hash() const; @@ -91,8 +96,11 @@ public: ResourceFormatImporter(); }; -class ResourceImporter : public Reference { - GDCLASS(ResourceImporter, Reference); +class ResourceImporter : public RefCounted { + GDCLASS(ResourceImporter, RefCounted); + +protected: + static void _bind_methods(); public: virtual String get_importer_name() const = 0; @@ -101,7 +109,8 @@ public: virtual String get_save_extension() const = 0; virtual String get_resource_type() const = 0; virtual float get_priority() const { return 1.0; } - virtual int get_import_order() const { return 0; } + virtual int get_import_order() const { return IMPORT_ORDER_DEFAULT; } + virtual int get_format_version() const { return 0; } struct ImportOption { PropertyInfo option; @@ -114,6 +123,14 @@ public: ImportOption() {} }; + enum ImportOrder { + IMPORT_ORDER_DEFAULT = 0, + IMPORT_ORDER_SCENE = 100, + }; + + virtual bool has_advanced_options() const { return false; } + virtual void show_advanced_options(const String &p_path) {} + virtual int get_preset_count() const { return 0; } virtual String get_preset_name(int p_idx) const { return String(); } @@ -122,10 +139,15 @@ public: 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; + virtual bool can_import_threaded() const { return true; } + virtual void import_threaded_begin() {} + virtual void import_threaded_end() {} virtual Error import_group_file(const String &p_group_file, const Map<String, Map<StringName, Variant>> &p_source_file_options, const Map<String, String> &p_base_paths) { return ERR_UNAVAILABLE; } virtual bool are_import_settings_valid(const String &p_path) const { return true; } virtual String get_import_settings_string() const { return String(); } }; +VARIANT_ENUM_CAST(ResourceImporter::ImportOrder); + #endif // RESOURCE_IMPORTER_H diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index b5c598e860..64237f3b15 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,13 +30,13 @@ #include "resource_loader.h" +#include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_importer.h" -#include "core/os/file_access.h" #include "core/os/os.h" -#include "core/print_string.h" -#include "core/project_settings.h" -#include "core/translation.h" -#include "core/variant_parser.h" +#include "core/string/print_string.h" +#include "core/string/translation.h" +#include "core/variant/variant_parser.h" #ifdef DEBUG_LOAD_THREADED #define print_lt(m_text) print_line(m_text) @@ -58,8 +58,8 @@ bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_ get_recognized_extensions_for_type(p_for_type, &extensions); } - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - if (E->get().nocasecmp_to(extension) == 0) { + for (const String &E : extensions) { + if (E.nocasecmp_to(extension) == 0) { return true; } } @@ -68,22 +68,33 @@ bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_ } bool ResourceFormatLoader::handles_type(const String &p_type) const { - if (get_script_instance() && get_script_instance()->has_method("handles_type")) { - // I guess custom loaders for custom resources should use "Resource" - return get_script_instance()->call("handles_type", p_type); + bool success; + if (GDVIRTUAL_CALL(_handles_type, p_type, success)) { + return success; } return false; } String ResourceFormatLoader::get_resource_type(const String &p_path) const { - if (get_script_instance() && get_script_instance()->has_method("get_resource_type")) { - return get_script_instance()->call("get_resource_type", p_path); + String ret; + + if (GDVIRTUAL_CALL(_get_resource_type, p_path, ret)) { + return ret; } return ""; } +ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const { + int64_t uid; + if (GDVIRTUAL_CALL(_get_resource_uid, p_path, uid)) { + return uid; + } + + return ResourceUID::INVALID_ID; +} + void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const { if (p_type == "" || handles_type(p_type)) { get_recognized_extensions(p_extensions); @@ -97,88 +108,84 @@ void ResourceLoader::get_recognized_extensions_for_type(const String &p_type, Li } bool ResourceFormatLoader::exists(const String &p_path) const { + bool success; + if (GDVIRTUAL_CALL(_exists, p_path, success)) { + return success; + } return FileAccess::exists(p_path); //by default just check file } void ResourceFormatLoader::get_recognized_extensions(List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("get_recognized_extensions"); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } -RES ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { - if (get_script_instance() && get_script_instance()->has_method("load")) { - Variant res = get_script_instance()->call("load", p_path, p_original_path, p_use_sub_threads); - - if (res.get_type() == Variant::INT) { +RES ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Variant res; + if (GDVIRTUAL_CALL(_load, p_path, p_original_path, p_use_sub_threads, p_cache_mode, res)) { + if (res.get_type() == Variant::INT) { // Error code, abort. if (r_error) { *r_error = (Error)res.operator int64_t(); } - - } else { + return RES(); + } else { // Success, pass on result. if (r_error) { *r_error = OK; } return res; } - - return res; } - ERR_FAIL_V_MSG(RES(), "Failed to load resource '" + p_path + "', ResourceFormatLoader::load was not implemented for this resource type."); + ERR_FAIL_V_MSG(RES(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type."); } void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { - if (get_script_instance() && get_script_instance()->has_method("get_dependencies")) { - PackedStringArray deps = get_script_instance()->call("get_dependencies", p_path, p_add_types); - - { - const String *r = deps.ptr(); - for (int i = 0; i < deps.size(); ++i) { - p_dependencies->push_back(r[i]); - } + PackedStringArray deps; + if (GDVIRTUAL_CALL(_get_dependencies, p_path, p_add_types, deps)) { + const String *r = deps.ptr(); + for (int i = 0; i < deps.size(); ++i) { + p_dependencies->push_back(r[i]); } } } Error ResourceFormatLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - if (get_script_instance() && get_script_instance()->has_method("rename_dependencies")) { - Dictionary deps_dict; - for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { - deps_dict[E->key()] = E->value(); - } + Dictionary deps_dict; + for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) { + deps_dict[E->key()] = E->value(); + } - int64_t res = get_script_instance()->call("rename_dependencies", deps_dict); - return (Error)res; + int64_t err; + if (GDVIRTUAL_CALL(_rename_dependencies, p_path, deps_dict, err)) { + return (Error)err; } return OK; } void ResourceFormatLoader::_bind_methods() { - { - MethodInfo info = MethodInfo(Variant::NIL, "load", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "original_path")); - info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - ClassDB::add_virtual_method(get_class_static(), info); - } - - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::PACKED_STRING_ARRAY, "get_recognized_extensions")); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "handles_type", PropertyInfo(Variant::STRING_NAME, "typename"))); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_resource_type", PropertyInfo(Variant::STRING, "path"))); - ClassDB::add_virtual_method(get_class_static(), MethodInfo("get_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "add_types"))); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "rename_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "renames"))); + BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); + BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); + BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + + GDVIRTUAL_BIND(_get_recognized_extensions); + GDVIRTUAL_BIND(_handles_type, "type"); + GDVIRTUAL_BIND(_get_resource_type, "path"); + GDVIRTUAL_BIND(_get_resource_uid, "path"); + GDVIRTUAL_BIND(_get_dependencies, "path", "add_types"); + GDVIRTUAL_BIND(_rename_dependencies, "path", "renames"); + GDVIRTUAL_BIND(_exists, "path"); + GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode"); } /////////////////////////////////// -RES ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, bool p_no_cache, Error *r_error, bool p_use_sub_threads, float *r_progress) { +RES ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { bool found = false; // Try all loaders and pick the first match for the type hint @@ -187,7 +194,7 @@ RES ResourceLoader::_load(const String &p_path, const String &p_original_path, c continue; } found = true; - RES res = loader[i]->load(p_path, p_original_path != String() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_no_cache); + RES res = loader[i]->load(p_path, p_original_path != String() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); if (res.is_null()) { continue; } @@ -211,10 +218,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.loader_id = Thread::get_caller_id(); if (load_task.semaphore) { - //this is an actual thread, so wait for Ok fom semaphore + //this is an actual thread, so wait for Ok from semaphore thread_load_semaphore->wait(); //wait until its ok to start loading } - load_task.resource = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, false, &load_task.error, load_task.use_sub_threads, &load_task.progress); + load_task.resource = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 @@ -267,13 +274,18 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { thread_load_mutex->unlock(); } -Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, const String &p_source_resource) { - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; +static String _validate_local_path(const String &p_path) { + ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); + if (uid != ResourceUID::INVALID_ID) { + return ResourceUID::get_singleton()->get_id_path(uid); + } else if (p_path.is_rel_path()) { + return "res://" + p_path; } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); + return ProjectSettings::get_singleton()->localize_path(p_path); } +} +Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode, const String &p_source_resource) { + String local_path = _validate_local_path(p_path); thread_load_mutex->lock(); @@ -314,6 +326,7 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); load_task.local_path = local_path; load_task.type_hint = p_type_hint; + load_task.cache_mode = p_cache_mode; load_task.use_sub_threads = p_use_sub_threads; { //must check if resource is already loaded before attempting to load it in a thread @@ -323,9 +336,7 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?"); } //lock first if possible - if (ResourceCache::lock) { - ResourceCache::lock->read_lock(); - } + ResourceCache::lock.read_lock(); //get ptr Resource **rptr = ResourceCache::resources.getptr(local_path); @@ -340,9 +351,7 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & load_task.progress = 1.0; } } - if (ResourceCache::lock) { - ResourceCache::lock->read_unlock(); - } + ResourceCache::lock.read_unlock(); } if (p_source_resource != String()) { @@ -354,7 +363,7 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & ThreadLoadTask &load_task = thread_load_tasks[local_path]; - if (load_task.resource.is_null()) { //needs to be loaded in thread + if (load_task.resource.is_null()) { //needs to be loaded in thread load_task.semaphore = memnew(Semaphore); if (thread_loading_count < thread_load_max) { @@ -366,7 +375,8 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & print_lt("REQUEST: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - load_task.thread = Thread::create(_thread_load_function, &thread_load_tasks[local_path]); + load_task.thread = memnew(Thread); + load_task.thread->start(_thread_load_function, &thread_load_tasks[local_path]); load_task.loader_id = load_task.thread->get_id(); } @@ -398,12 +408,7 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); - } + String local_path = _validate_local_path(p_path); thread_load_mutex->lock(); if (!thread_load_tasks.has(local_path)) { @@ -423,12 +428,7 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const } RES ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); - } + String local_path = _validate_local_path(p_path); thread_load_mutex->lock(); if (!thread_load_tasks.has(local_path)) { @@ -441,7 +441,7 @@ RES ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { ThreadLoadTask &load_task = thread_load_tasks[local_path]; - //semaphore still exists, meaning its still loading, request poll + //semaphore still exists, meaning it's still loading, request poll Semaphore *semaphore = load_task.semaphore; if (semaphore) { load_task.poll_requests++; @@ -450,7 +450,7 @@ RES ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { // As we got a semaphore, this means we are going to have to wait // until the sub-resource is done loading // - // As this thread will become 'blocked' we should "echange" its + // As this thread will become 'blocked' we should "exchange" its // active status with a waiting one, to ensure load continues. // // This ensures loading is never blocked and that is also within @@ -493,7 +493,7 @@ RES ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { if (load_task.requests == 0) { if (load_task.thread) { //thread may not have been used - Thread::wait_to_finish(load_task.thread); + load_task.thread->wait_to_finish(); memdelete(load_task.thread); } thread_load_tasks.erase(local_path); @@ -504,19 +504,14 @@ RES ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { return resource; } -RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p_no_cache, Error *r_error) { +RES ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { if (r_error) { *r_error = ERR_CANT_OPEN; } - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); - } + String local_path = _validate_local_path(p_path); - if (!p_no_cache) { + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { thread_load_mutex->lock(); //Is it already being loaded? poll until done @@ -535,9 +530,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p } //Is it cached? - if (ResourceCache::lock) { - ResourceCache::lock->read_lock(); - } + ResourceCache::lock.read_lock(); Resource **rptr = ResourceCache::resources.getptr(local_path); @@ -546,9 +539,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p //it is possible this resource was just freed in a thread. If so, this referencing will not work and resource is considered not cached if (res.is_valid()) { - if (ResourceCache::lock) { - ResourceCache::lock->read_unlock(); - } + ResourceCache::lock.read_unlock(); thread_load_mutex->unlock(); if (r_error) { @@ -559,9 +550,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p } } - if (ResourceCache::lock) { - ResourceCache::lock->read_unlock(); - } + ResourceCache::lock.read_unlock(); //load using task (but this thread) ThreadLoadTask load_task; @@ -570,6 +559,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p load_task.local_path = local_path; load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); load_task.type_hint = p_type_hint; + load_task.cache_mode = p_cache_mode; //ignore load_task.loader_id = Thread::get_caller_id(); thread_load_tasks[local_path] = load_task; @@ -590,7 +580,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p print_verbose("Loading resource: " + path); float p; - RES res = _load(path, local_path, p_type_hint, p_no_cache, r_error, false, &p); + RES res = _load(path, local_path, p_type_hint, p_cache_mode, r_error, false, &p); if (res.is_null()) { print_verbose("Failed loading resource: " + path); @@ -616,12 +606,7 @@ RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p } bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) { - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); - } + String local_path = _validate_local_path(p_path); if (ResourceCache::has(local_path)) { return true; // If cached, it probably exists @@ -681,14 +666,7 @@ void ResourceLoader::remove_resource_format_loader(Ref<ResourceFormatLoader> p_f } int ResourceLoader::get_import_order(const String &p_path) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -706,14 +684,7 @@ int ResourceLoader::get_import_order(const String &p_path) { } String ResourceLoader::get_import_group_file(const String &p_path) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -731,14 +702,7 @@ String ResourceLoader::get_import_group_file(const String &p_path) { } bool ResourceLoader::is_import_valid(const String &p_path) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -756,14 +720,7 @@ bool ResourceLoader::is_import_valid(const String &p_path) { } bool ResourceLoader::is_imported(const String &p_path) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -781,14 +738,7 @@ bool ResourceLoader::is_imported(const String &p_path) { } void ResourceLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -804,14 +754,7 @@ void ResourceLoader::get_dependencies(const String &p_path, List<String> *p_depe } Error ResourceLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) { - String path = _path_remap(p_path); - - String local_path; - if (path.is_rel_path()) { - local_path = "res://" + path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(path); - } + String local_path = _path_remap(_validate_local_path(p_path)); for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(local_path)) { @@ -829,12 +772,7 @@ Error ResourceLoader::rename_dependencies(const String &p_path, const Map<String } String ResourceLoader::get_resource_type(const String &p_path) { - String local_path; - if (p_path.is_rel_path()) { - local_path = "res://" + p_path; - } else { - local_path = ProjectSettings::get_singleton()->localize_path(p_path); - } + String local_path = _validate_local_path(p_path); for (int i = 0; i < loader_count; i++) { String result = loader[i]->get_resource_type(local_path); @@ -846,6 +784,19 @@ String ResourceLoader::get_resource_type(const String &p_path) { return ""; } +ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) { + String local_path = _validate_local_path(p_path); + + for (int i = 0; i < loader_count; i++) { + ResourceUID::ID id = loader[i]->get_resource_uid(local_path); + if (id != ResourceUID::INVALID_ID) { + return id; + } + } + + return ResourceUID::INVALID_ID; +} + String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_remapped) { String new_path = p_path; @@ -871,7 +822,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem continue; } - String l = res_remaps[i].right(split + 1).strip_edges(); + String l = res_remaps[i].substr(split + 1).strip_edges(); if (l == locale) { // Exact match. new_path = res_remaps[i].left(split); break; @@ -955,9 +906,7 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - if (ResourceCache::lock) { - ResourceCache::lock->read_lock(); - } + ResourceCache::lock.read_lock(); List<Resource *> to_reload; SelfList<Resource> *E = remapped_list.first(); @@ -967,9 +916,7 @@ void ResourceLoader::reload_translation_remaps() { E = E->next(); } - if (ResourceCache::lock) { - ResourceCache::lock->read_unlock(); - } + ResourceCache::lock.read_unlock(); //now just make sure to not delete any of these resources while changing locale.. while (to_reload.front()) { @@ -979,22 +926,22 @@ void ResourceLoader::reload_translation_remaps() { } void ResourceLoader::load_translation_remaps() { - if (!ProjectSettings::get_singleton()->has_setting("locale/translation_remaps")) { + if (!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) { return; } - Dictionary remaps = ProjectSettings::get_singleton()->get("locale/translation_remaps"); + Dictionary remaps = ProjectSettings::get_singleton()->get("internationalization/locale/translation_remaps"); List<Variant> keys; remaps.get_key_list(&keys); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - Array langs = remaps[E->get()]; + for (const Variant &E : keys) { + Array langs = remaps[E]; Vector<String> lang_remaps; lang_remaps.resize(langs.size()); for (int i = 0; i < langs.size(); i++) { lang_remaps.write[i] = langs[i]; } - translation_remaps[String(E->get())] = lang_remaps; + translation_remaps[String(E)] = lang_remaps; } } @@ -1053,11 +1000,11 @@ bool ResourceLoader::add_custom_resource_format_loader(String script_path) { bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatLoader"); ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceLoader: " + script_path + "."); - Object *obj = ClassDB::instance(ibt); + Object *obj = ClassDB::instantiate(ibt); ERR_FAIL_COND_V_MSG(obj == nullptr, false, "Cannot instance script as custom resource loader, expected 'ResourceFormatLoader' inheritance, got: " + String(ibt) + "."); - ResourceFormatLoader *crl = Object::cast_to<ResourceFormatLoader>(obj); + Ref<ResourceFormatLoader> crl = Object::cast_to<ResourceFormatLoader>(obj); crl->set_script(s); ResourceLoader::add_resource_format_loader(crl); @@ -1079,8 +1026,7 @@ void ResourceLoader::add_custom_loaders() { List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); - for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { - StringName class_name = E->get(); + for (const StringName &class_name : global_classes) { StringName base_class = ScriptServer::get_global_class_native_base(class_name); if (base_class == custom_loader_base_class) { diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 9322b5273a..f1d9815635 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,24 +31,44 @@ #ifndef RESOURCE_LOADER_H #define RESOURCE_LOADER_H +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" #include "core/os/semaphore.h" #include "core/os/thread.h" -#include "core/resource.h" -class ResourceFormatLoader : public Reference { - GDCLASS(ResourceFormatLoader, Reference); +class ResourceFormatLoader : public RefCounted { + GDCLASS(ResourceFormatLoader, RefCounted); + +public: + enum CacheMode { + CACHE_MODE_IGNORE, // Resource and subresources do not use path cache, no path is set into resource. + CACHE_MODE_REUSE, // Resource and subresources use patch cache, reuse existing loaded resources instead of loading from disk when available. + CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk. + }; protected: static void _bind_methods(); + GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions) + GDVIRTUAL1RC(bool, _handles_type, StringName) + GDVIRTUAL1RC(String, _get_resource_type, String) + GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String) + GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool) + GDVIRTUAL2RC(int64_t, _rename_dependencies, String, Dictionary) + GDVIRTUAL1RC(bool, _exists, String) + + GDVIRTUAL4RC(Variant, _load, String, String, bool, int) + public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual bool exists(const String &p_path) const; virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const; virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); virtual Error rename_dependencies(const String &p_path, const Map<String, String> &p_map); virtual bool is_import_valid(const String &p_path) const { return true; } @@ -59,6 +79,8 @@ public: virtual ~ResourceFormatLoader() {} }; +VARIANT_ENUM_CAST(ResourceFormatLoader::CacheMode) + typedef void (*ResourceLoadErrorNotify)(void *p_ud, const String &p_text); typedef void (*DependencyErrorNotify)(void *p_ud, const String &p_loading, const String &p_which, const String &p_type); @@ -98,8 +120,8 @@ private: friend class ResourceFormatImporter; friend class ResourceInteractiveLoader; - //internal load function - static RES _load(const String &p_path, const String &p_original_path, const String &p_type_hint, bool p_no_cache, Error *r_error, bool p_use_sub_threads, float *r_progress); + // Internal load function. + static RES _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress); static ResourceLoadedCallback _loaded_callback; @@ -114,6 +136,7 @@ private: String type_hint; float progress = 0.0; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; + ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; Error error = OK; RES resource; bool xl_remapped = false; @@ -136,17 +159,18 @@ private: static float _dependency_get_progress(const String &p_path); public: - static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, const String &p_source_resource = String()); + static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, const String &p_source_resource = String()); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); static RES load_threaded_get(const String &p_path, Error *r_error = nullptr); - static RES load(const String &p_path, const String &p_type_hint = "", bool p_no_cache = false, Error *r_error = nullptr); + static RES load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr); static bool exists(const String &p_path, const String &p_type_hint = ""); static void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions); static void add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front = false); static void remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader); static String get_resource_type(const String &p_path); + static ResourceUID::ID get_resource_uid(const String &p_path); static void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); static Error rename_dependencies(const String &p_path, const Map<String, String> &p_map); static bool is_import_valid(const String &p_path); diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index a8da215b61..823b5f75b1 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,56 +29,50 @@ /*************************************************************************/ #include "resource_saver.h" +#include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" -#include "core/script_language.h" +#include "core/object/script_language.h" Ref<ResourceFormatSaver> ResourceSaver::saver[MAX_SAVERS]; int ResourceSaver::saver_count = 0; bool ResourceSaver::timestamp_on_save = false; ResourceSavedCallback ResourceSaver::save_callback = nullptr; +ResourceSaverGetResourceIDForPath ResourceSaver::save_get_id_for_path = nullptr; Error ResourceFormatSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - if (get_script_instance() && get_script_instance()->has_method("save")) { - return (Error)get_script_instance()->call("save", p_path, p_resource, p_flags).operator int64_t(); + int64_t res; + if (GDVIRTUAL_CALL(_save, p_path, p_resource, p_flags, res)) { + return (Error)res; } return ERR_METHOD_NOT_FOUND; } bool ResourceFormatSaver::recognize(const RES &p_resource) const { - if (get_script_instance() && get_script_instance()->has_method("recognize")) { - return get_script_instance()->call("recognize", p_resource); + bool success; + if (GDVIRTUAL_CALL(_recognize, p_resource, success)) { + return success; } return false; } void ResourceFormatSaver::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (get_script_instance() && get_script_instance()->has_method("get_recognized_extensions")) { - PackedStringArray exts = get_script_instance()->call("get_recognized_extensions", p_resource); - - { - const String *r = exts.ptr(); - for (int i = 0; i < exts.size(); ++i) { - p_extensions->push_back(r[i]); - } + PackedStringArray exts; + if (GDVIRTUAL_CALL(_get_recognized_extensions, p_resource, exts)) { + const String *r = exts.ptr(); + for (int i = 0; i < exts.size(); ++i) { + p_extensions->push_back(r[i]); } } } void ResourceFormatSaver::_bind_methods() { - { - PropertyInfo arg0 = PropertyInfo(Variant::STRING, "path"); - PropertyInfo arg1 = PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"); - PropertyInfo arg2 = PropertyInfo(Variant::INT, "flags"); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "save", arg0, arg1, arg2)); - } - - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::PACKED_STRING_ARRAY, "get_recognized_extensions", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "recognize", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); + GDVIRTUAL_BIND(_save, "path", "resource", "flags"); + GDVIRTUAL_BIND(_recognize, "resource"); + GDVIRTUAL_BIND(_get_recognized_extensions, "resource"); } Error ResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { @@ -94,8 +88,8 @@ Error ResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t bool recognized = false; saver[i]->get_recognized_extensions(p_resource, &extensions); - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - if (E->get().nocasecmp_to(extension) == 0) { + for (const String &E : extensions) { + if (E.nocasecmp_to(extension) == 0) { recognized = true; } } @@ -210,11 +204,11 @@ bool ResourceSaver::add_custom_resource_format_saver(String script_path) { bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatSaver"); ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceSaver: " + script_path + "."); - Object *obj = ClassDB::instance(ibt); + Object *obj = ClassDB::instantiate(ibt); ERR_FAIL_COND_V_MSG(obj == nullptr, false, "Cannot instance script as custom resource saver, expected 'ResourceFormatSaver' inheritance, got: " + String(ibt) + "."); - ResourceFormatSaver *crl = Object::cast_to<ResourceFormatSaver>(obj); + Ref<ResourceFormatSaver> crl = Object::cast_to<ResourceFormatSaver>(obj); crl->set_script(s); ResourceSaver::add_resource_format_saver(crl); @@ -236,8 +230,7 @@ void ResourceSaver::add_custom_savers() { List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); - for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { - StringName class_name = E->get(); + for (const StringName &class_name : global_classes) { StringName base_class = ScriptServer::get_global_class_native_base(class_name); if (base_class == custom_saver_base_class) { @@ -259,3 +252,14 @@ void ResourceSaver::remove_custom_savers() { remove_resource_format_saver(custom_savers[i]); } } + +ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) { + if (save_get_id_for_path) { + return save_get_id_for_path(p_path, p_generate); + } + return ResourceUID::INVALID_ID; +} + +void ResourceSaver::set_get_resource_id_for_path(ResourceSaverGetResourceIDForPath p_callback) { + save_get_id_for_path = p_callback; +} diff --git a/core/io/resource_saver.h b/core/io/resource_saver.h index 8b4cdd86f8..fcde835dab 100644 --- a/core/io/resource_saver.h +++ b/core/io/resource_saver.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,14 +31,20 @@ #ifndef RESOURCE_SAVER_H #define RESOURCE_SAVER_H -#include "core/resource.h" +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" -class ResourceFormatSaver : public Reference { - GDCLASS(ResourceFormatSaver, Reference); +class ResourceFormatSaver : public RefCounted { + GDCLASS(ResourceFormatSaver, RefCounted); protected: static void _bind_methods(); + GDVIRTUAL3R(int64_t, _save, String, RES, uint32_t) + GDVIRTUAL1RC(bool, _recognize, RES) + GDVIRTUAL1RC(Vector<String>, _get_recognized_extensions, RES) + public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual bool recognize(const RES &p_resource) const; @@ -48,6 +54,7 @@ public: }; typedef void (*ResourceSavedCallback)(Ref<Resource> p_resource, const String &p_path); +typedef ResourceUID::ID (*ResourceSaverGetResourceIDForPath)(const String &p_path, bool p_generate); class ResourceSaver { enum { @@ -58,12 +65,12 @@ class ResourceSaver { static int saver_count; static bool timestamp_on_save; static ResourceSavedCallback save_callback; + static ResourceSaverGetResourceIDForPath save_get_id_for_path; static Ref<ResourceFormatSaver> _find_custom_resource_format_saver(String path); public: enum SaverFlags { - FLAG_RELATIVE_PATHS = 1, FLAG_BUNDLE_RESOURCES = 2, FLAG_CHANGE_PATH = 4, @@ -81,7 +88,10 @@ public: static void set_timestamp_on_save(bool p_timestamp) { timestamp_on_save = p_timestamp; } static bool get_timestamp_on_save() { return timestamp_on_save; } + static ResourceUID::ID get_resource_id_for_path(const String &p_path, bool p_generate = false); + static void set_save_callback(ResourceSavedCallback p_callback); + static void set_get_resource_id_for_path(ResourceSaverGetResourceIDForPath p_callback); static bool add_custom_resource_format_saver(String script_path); static void remove_custom_resource_format_saver(String script_path); diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp new file mode 100644 index 0000000000..97d683f415 --- /dev/null +++ b/core/io/resource_uid.cpp @@ -0,0 +1,262 @@ +/*************************************************************************/ +/* resource_uid.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 "resource_uid.h" +#include "core/crypto/crypto.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" + +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::id_to_text(ID p_id) const { + if (p_id < 0) { + return "uid://<invalid>"; + } + String txt; + + while (p_id) { + uint32_t c = p_id % base; + if (c < char_count) { + txt = String::chr('a' + c) + txt; + } else { + txt = String::chr('0' + (c - char_count)) + txt; + } + p_id /= base; + } + + return "uid://" + txt; +} + +ResourceUID::ID ResourceUID::text_to_id(const String &p_text) const { + if (!p_text.begins_with("uid://") || p_text == "uid://<invalid>") { + return INVALID_ID; + } + + uint32_t l = p_text.length(); + uint64_t uid = 0; + for (uint32_t i = 6; i < l; i++) { + uid *= base; + uint32_t c = p_text[i]; + if (c >= 'a' && c <= 'z') { + uid += c - 'a'; + } else if (c >= '0' && c <= '9') { + uid += c - '0' + char_count; + } else { + return INVALID_ID; + } + } + return ID(uid & 0x7FFFFFFFFFFFFFFF); +} + +ResourceUID::ID ResourceUID::create_id() const { + mutex.lock(); + if (crypto.is_null()) { + crypto = Ref<Crypto>(Crypto::create()); + } + mutex.unlock(); + while (true) { + PackedByteArray bytes = crypto->generate_random_bytes(8); + ERR_FAIL_COND_V(bytes.size() != 8, INVALID_ID); + const uint64_t *ptr64 = (const uint64_t *)bytes.ptr(); + ID id = int64_t((*ptr64) & 0x7FFFFFFFFFFFFFFF); + mutex.lock(); + bool exists = unique_ids.has(id); + mutex.unlock(); + if (!exists) { + return id; + } + } +} + +bool ResourceUID::has_id(ID p_id) const { + MutexLock l(mutex); + return unique_ids.has(p_id); +} +void ResourceUID::add_id(ID p_id, const String &p_path) { + MutexLock l(mutex); + ERR_FAIL_COND(unique_ids.has(p_id)); + Cache c; + c.cs = p_path.utf8(); + unique_ids[p_id] = c; + changed = true; +} + +void ResourceUID::set_id(ID p_id, const String &p_path) { + MutexLock l(mutex); + ERR_FAIL_COND(!unique_ids.has(p_id)); + CharString cs = p_path.utf8(); + if (strcmp(cs.ptr(), unique_ids[p_id].cs.ptr()) != 0) { + unique_ids[p_id].cs = cs; + unique_ids[p_id].saved_to_cache = false; //changed + changed = true; + } +} + +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; +} +void ResourceUID::remove_id(ID p_id) { + MutexLock l(mutex); + ERR_FAIL_COND(!unique_ids.has(p_id)); + unique_ids.erase(p_id); +} + +Error ResourceUID::save_to_cache() { + 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 + } + + FileAccessRef f = FileAccess::open(CACHE_FILE, FileAccess::WRITE); + if (!f) { + return ERR_CANT_OPEN; + } + + MutexLock l(mutex); + f->store_32(unique_ids.size()); + + cache_entries = 0; + + for (OrderedHashMap<ID, Cache>::Element E = unique_ids.front(); E; E = E.next()) { + f->store_64(E.key()); + uint32_t s = E.get().cs.length(); + f->store_32(s); + f->store_buffer((const uint8_t *)E.get().cs.ptr(), s); + E.get().saved_to_cache = true; + cache_entries++; + } + + changed = false; + return OK; +} + +Error ResourceUID::load_from_cache() { + FileAccessRef f = FileAccess::open(CACHE_FILE, FileAccess::READ); + if (!f) { + return ERR_CANT_OPEN; + } + + MutexLock l(mutex); + unique_ids.clear(); + + uint32_t entry_count = f->get_32(); + for (uint32_t i = 0; i < entry_count; i++) { + int64_t id = f->get_64(); + int32_t len = f->get_32(); + Cache c; + c.cs.resize(len + 1); + ERR_FAIL_COND_V(c.cs.size() != len + 1, ERR_FILE_CORRUPT); // out of memory + c.cs[len] = 0; + int32_t rl = f->get_buffer((uint8_t *)c.cs.ptrw(), len); + ERR_FAIL_COND_V(rl != len, ERR_FILE_CORRUPT); + + c.saved_to_cache = true; + unique_ids[id] = c; + } + + cache_entries = entry_count; + changed = false; + return OK; +} + +Error ResourceUID::update_cache() { + if (!changed) { + return OK; + } + + if (cache_entries == 0) { + return save_to_cache(); + } + MutexLock l(mutex); + + FileAccess *f = nullptr; + 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 + if (!f) { + return ERR_CANT_OPEN; + } + f->seek_end(); + } + f->store_64(E.key()); + uint32_t s = E.get().cs.length(); + f->store_32(s); + f->store_buffer((const uint8_t *)E.get().cs.ptr(), s); + E.get().saved_to_cache = true; + cache_entries++; + } + } + + if (f != nullptr) { + f->seek(0); + f->store_32(cache_entries); //update amount of entries + f->close(); + memdelete(f); + } + + changed = false; + + return OK; +} + +void ResourceUID::clear() { + cache_entries = 0; + unique_ids.clear(); + changed = false; +} +void ResourceUID::_bind_methods() { + ClassDB::bind_method(D_METHOD("id_to_text", "id"), &ResourceUID::id_to_text); + ClassDB::bind_method(D_METHOD("text_to_id", "text_id"), &ResourceUID::text_to_id); + + ClassDB::bind_method(D_METHOD("create_id"), &ResourceUID::create_id); + + ClassDB::bind_method(D_METHOD("has_id", "id"), &ResourceUID::has_id); + ClassDB::bind_method(D_METHOD("add_id", "id", "path"), &ResourceUID::add_id); + ClassDB::bind_method(D_METHOD("set_id", "id", "path"), &ResourceUID::set_id); + ClassDB::bind_method(D_METHOD("get_id_path", "id"), &ResourceUID::get_id_path); + ClassDB::bind_method(D_METHOD("remove_id", "id"), &ResourceUID::remove_id); + + BIND_CONSTANT(INVALID_ID) +} +ResourceUID *ResourceUID::singleton = nullptr; +ResourceUID::ResourceUID() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; +} +ResourceUID::~ResourceUID() { +} diff --git a/core/io/file_access_buffered.h b/core/io/resource_uid.h index 99d5ce903d..b12138425a 100644 --- a/core/io/file_access_buffered.h +++ b/core/io/resource_uid.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* file_access_buffered.h */ +/* resource_uid.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,64 +28,62 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef FILE_ACCESS_BUFFERED_H -#define FILE_ACCESS_BUFFERED_H +#ifndef RESOURCE_UUID_H +#define RESOURCE_UUID_H -#include "core/os/file_access.h" +#include "core/object/ref_counted.h" +#include "core/string/string_name.h" +#include "core/templates/ordered_hash_map.h" -#include "core/ustring.h" - -class FileAccessBuffered : public FileAccess { +class Crypto; +class ResourceUID : public Object { + GDCLASS(ResourceUID, Object) public: + typedef int64_t ID; enum { - DEFAULT_CACHE_SIZE = 128 * 1024, + INVALID_ID = -1 }; -private: - int cache_size = DEFAULT_CACHE_SIZE; - - int cache_data_left() const; - mutable Error last_error; - -protected: - Error set_error(Error p_error) const; + static const char *CACHE_FILE; - mutable struct File { - bool open = false; - int size = 0; - int offset = 0; - String name; - int access_flags = 0; - } file; +private: + mutable Ref<Crypto> crypto; + Mutex mutex; + struct Cache { + CharString cs; + bool saved_to_cache = false; + }; - mutable struct Cache { - Vector<uint8_t> buffer; - int offset = 0; - } cache; + OrderedHashMap<ID, Cache> unique_ids; //unique IDs and utf8 paths (less memory used) + static ResourceUID *singleton; - virtual int read_data_block(int p_offset, int p_size, uint8_t *p_dest = nullptr) const = 0; + uint32_t cache_entries = 0; + bool changed = false; - void set_cache_size(int p_size); - int get_cache_size(); +protected: + static void _bind_methods(); public: - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file - - virtual void seek(size_t p_position); ///< seek to a given position - virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file + String id_to_text(ID p_id) const; + ID text_to_id(const String &p_text) const; - virtual bool eof_reached() const; + ID create_id() const; + bool has_id(ID p_id) const; + void add_id(ID p_id, const String &p_path); + void set_id(ID p_id, const String &p_path); + String get_id_path(ID p_id) const; + void remove_id(ID p_id); - virtual uint8_t get_8() const; - virtual int get_buffer(uint8_t *p_dest, int p_length) const; ///< get an array of bytes + Error load_from_cache(); + Error save_to_cache(); + Error update_cache(); - virtual bool is_open() const; + void clear(); - virtual Error get_error() const; + static ResourceUID *get_singleton() { return singleton; } - FileAccessBuffered() {} - virtual ~FileAccessBuffered() {} + ResourceUID(); + ~ResourceUID(); }; -#endif +#endif // RESOURCEUUID_H diff --git a/core/io/stream_peer.cpp b/core/io/stream_peer.cpp index 403f61bb24..27f8d4e88f 100644 --- a/core/io/stream_peer.cpp +++ b/core/io/stream_peer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -108,8 +108,8 @@ Array StreamPeer::_get_partial_data(int p_bytes) { return ret; } -void StreamPeer::set_big_endian(bool p_enable) { - big_endian = p_enable; +void StreamPeer::set_big_endian(bool p_big_endian) { + big_endian = p_big_endian; } bool StreamPeer::is_big_endian_enabled() const { @@ -433,7 +433,7 @@ Error StreamPeerBuffer::put_data(const uint8_t *p_data, int p_bytes) { } uint8_t *w = data.ptrw(); - copymem(&w[pointer], p_data, p_bytes); + memcpy(&w[pointer], p_data, p_bytes); pointer += p_bytes; return OK; @@ -466,7 +466,7 @@ Error StreamPeerBuffer::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_ } const uint8_t *r = data.ptr(); - copymem(p_buffer, r + pointer, r_received); + memcpy(p_buffer, r + pointer, r_received); pointer += r_received; // FIXME: return what? OK or ERR_* @@ -512,7 +512,7 @@ void StreamPeerBuffer::clear() { Ref<StreamPeerBuffer> StreamPeerBuffer::duplicate() const { Ref<StreamPeerBuffer> spb; - spb.instance(); + spb.instantiate(); spb->data = data; return spb; } diff --git a/core/io/stream_peer.h b/core/io/stream_peer.h index 39097a57f2..effc3850af 100644 --- a/core/io/stream_peer.h +++ b/core/io/stream_peer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,10 +31,10 @@ #ifndef STREAM_PEER_H #define STREAM_PEER_H -#include "core/reference.h" +#include "core/object/ref_counted.h" -class StreamPeer : public Reference { - GDCLASS(StreamPeer, Reference); +class StreamPeer : public RefCounted { + GDCLASS(StreamPeer, RefCounted); OBJ_CATEGORY("Networking"); protected: @@ -58,7 +58,7 @@ public: virtual int get_available_bytes() const = 0; - void set_big_endian(bool p_enable); + void set_big_endian(bool p_big_endian); bool is_big_endian_enabled() const; void put_8(int8_t p_val); diff --git a/core/io/stream_peer_ssl.cpp b/core/io/stream_peer_ssl.cpp index 3dc31c6769..6f5a06474c 100644 --- a/core/io/stream_peer_ssl.cpp +++ b/core/io/stream_peer_ssl.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,7 +30,7 @@ #include "stream_peer_ssl.h" -#include "core/engine.h" +#include "core/config/engine.h" StreamPeerSSL *(*StreamPeerSSL::_create)() = nullptr; diff --git a/core/io/stream_peer_ssl.h b/core/io/stream_peer_ssl.h index 81b95b856d..1df9ced08c 100644 --- a/core/io/stream_peer_ssl.h +++ b/core/io/stream_peer_ssl.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/core/io/stream_peer_tcp.cpp b/core/io/stream_peer_tcp.cpp index cce728c30a..5b794274ca 100644 --- a/core/io/stream_peer_tcp.cpp +++ b/core/io/stream_peer_tcp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,7 +30,7 @@ #include "stream_peer_tcp.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" Error StreamPeerTCP::_poll_connection() { ERR_FAIL_COND_V(status != STATUS_CONNECTING || !_sock.is_valid() || !_sock->is_open(), FAILED); @@ -56,7 +56,7 @@ Error StreamPeerTCP::_poll_connection() { return ERR_CONNECTION_ERROR; } -void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IP_Address p_host, uint16_t p_port) { +void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port) { _sock = p_sock; _sock->set_blocking_enabled(false); @@ -67,21 +67,40 @@ void StreamPeerTCP::accept_socket(Ref<NetSocket> p_sock, IP_Address p_host, uint peer_port = p_port; } -Error StreamPeerTCP::connect_to_host(const IP_Address &p_host, uint16_t p_port) { +Error StreamPeerTCP::bind(int p_port, const IPAddress &p_host) { ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); - ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive)."); - Error err; IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; + if (p_host.is_wildcard()) { + ip_type = IP::TYPE_ANY; + } + Error err = _sock->open(NetSocket::TYPE_TCP, ip_type); + if (err != OK) { + return err; + } + _sock->set_blocking_enabled(false); + return _sock->bind(p_host, p_port); +} - err = _sock->open(NetSocket::TYPE_TCP, ip_type); - ERR_FAIL_COND_V(err != OK, FAILED); +Error StreamPeerTCP::connect_to_host(const IPAddress &p_host, int p_port) { + ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); + ERR_FAIL_COND_V(status != STATUS_NONE, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(!p_host.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, ERR_INVALID_PARAMETER, "The remote port number must be between 1 and 65535 (inclusive)."); - _sock->set_blocking_enabled(false); + if (!_sock->is_open()) { + IP::Type ip_type = p_host.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6; + Error err = _sock->open(NetSocket::TYPE_TCP, ip_type); + if (err != OK) { + return err; + } + _sock->set_blocking_enabled(false); + } timeout = OS::get_singleton()->get_ticks_msec() + (((uint64_t)GLOBAL_GET("network/limits/tcp/connect_timeout_seconds")) * 1000); - err = _sock->connect_to_host(p_host, p_port); + Error err = _sock->connect_to_host(p_host, p_port); if (err == OK) { status = STATUS_CONNECTED; @@ -264,7 +283,7 @@ void StreamPeerTCP::disconnect_from_host() { timeout = 0; status = STATUS_NONE; - peer_host = IP_Address(); + peer_host = IPAddress(); peer_port = 0; } @@ -296,16 +315,22 @@ int StreamPeerTCP::get_available_bytes() const { return _sock->get_available_bytes(); } -IP_Address StreamPeerTCP::get_connected_host() const { +IPAddress StreamPeerTCP::get_connected_host() const { return peer_host; } -uint16_t StreamPeerTCP::get_connected_port() const { +int StreamPeerTCP::get_connected_port() const { return peer_port; } +int StreamPeerTCP::get_local_port() const { + uint16_t local_port; + _sock->get_socket_address(nullptr, &local_port); + return local_port; +} + Error StreamPeerTCP::_connect(const String &p_address, int p_port) { - IP_Address ip; + IPAddress ip; if (p_address.is_valid_ip_address()) { ip = p_address; } else { @@ -319,11 +344,13 @@ Error StreamPeerTCP::_connect(const String &p_address, int p_port) { } void StreamPeerTCP::_bind_methods() { + ClassDB::bind_method(D_METHOD("bind", "port", "host"), &StreamPeerTCP::bind, DEFVAL("*")); ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port"), &StreamPeerTCP::_connect); ClassDB::bind_method(D_METHOD("is_connected_to_host"), &StreamPeerTCP::is_connected_to_host); ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTCP::get_status); ClassDB::bind_method(D_METHOD("get_connected_host"), &StreamPeerTCP::get_connected_host); ClassDB::bind_method(D_METHOD("get_connected_port"), &StreamPeerTCP::get_connected_port); + ClassDB::bind_method(D_METHOD("get_local_port"), &StreamPeerTCP::get_local_port); ClassDB::bind_method(D_METHOD("disconnect_from_host"), &StreamPeerTCP::disconnect_from_host); ClassDB::bind_method(D_METHOD("set_no_delay", "enabled"), &StreamPeerTCP::set_no_delay); diff --git a/core/io/stream_peer_tcp.h b/core/io/stream_peer_tcp.h index 45205866b4..a2a7f447d8 100644 --- a/core/io/stream_peer_tcp.h +++ b/core/io/stream_peer_tcp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,7 +42,6 @@ class StreamPeerTCP : public StreamPeer { public: enum Status { - STATUS_NONE, STATUS_CONNECTING, STATUS_CONNECTED, @@ -53,7 +52,7 @@ protected: Ref<NetSocket> _sock; uint64_t timeout = 0; Status status = STATUS_NONE; - IP_Address peer_host; + IPAddress peer_host; uint16_t peer_port = 0; Error _connect(const String &p_address, int p_port); @@ -64,12 +63,14 @@ protected: static void _bind_methods(); public: - void accept_socket(Ref<NetSocket> p_sock, IP_Address p_host, uint16_t p_port); + void accept_socket(Ref<NetSocket> p_sock, IPAddress p_host, uint16_t p_port); - Error connect_to_host(const IP_Address &p_host, uint16_t p_port); + Error bind(int p_port, const IPAddress &p_host); + Error connect_to_host(const IPAddress &p_host, int p_port); bool is_connected_to_host() const; - IP_Address get_connected_host() const; - uint16_t get_connected_port() const; + IPAddress get_connected_host() const; + int get_connected_port() const; + int get_local_port() const; void disconnect_from_host(); int get_available_bytes() const override; diff --git a/core/io/tcp_server.cpp b/core/io/tcp_server.cpp index d7061b6bf4..5e0c0390f9 100644 --- a/core/io/tcp_server.cpp +++ b/core/io/tcp_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,15 +30,16 @@ #include "tcp_server.h" -void TCP_Server::_bind_methods() { - ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &TCP_Server::listen, DEFVAL("*")); - ClassDB::bind_method(D_METHOD("is_connection_available"), &TCP_Server::is_connection_available); - ClassDB::bind_method(D_METHOD("is_listening"), &TCP_Server::is_listening); - ClassDB::bind_method(D_METHOD("take_connection"), &TCP_Server::take_connection); - ClassDB::bind_method(D_METHOD("stop"), &TCP_Server::stop); +void TCPServer::_bind_methods() { + ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &TCPServer::listen, DEFVAL("*")); + ClassDB::bind_method(D_METHOD("is_connection_available"), &TCPServer::is_connection_available); + ClassDB::bind_method(D_METHOD("is_listening"), &TCPServer::is_listening); + ClassDB::bind_method(D_METHOD("get_local_port"), &TCPServer::get_local_port); + ClassDB::bind_method(D_METHOD("take_connection"), &TCPServer::take_connection); + ClassDB::bind_method(D_METHOD("stop"), &TCPServer::stop); } -Error TCP_Server::listen(uint16_t p_port, const IP_Address &p_bind_address) { +Error TCPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER); @@ -74,13 +75,19 @@ Error TCP_Server::listen(uint16_t p_port, const IP_Address &p_bind_address) { return OK; } -bool TCP_Server::is_listening() const { +int TCPServer::get_local_port() const { + uint16_t local_port; + _sock->get_socket_address(nullptr, &local_port); + return local_port; +} + +bool TCPServer::is_listening() const { ERR_FAIL_COND_V(!_sock.is_valid(), false); return _sock->is_open(); } -bool TCP_Server::is_connection_available() const { +bool TCPServer::is_connection_available() const { ERR_FAIL_COND_V(!_sock.is_valid(), false); if (!_sock->is_open()) { @@ -91,14 +98,14 @@ bool TCP_Server::is_connection_available() const { return (err == OK); } -Ref<StreamPeerTCP> TCP_Server::take_connection() { +Ref<StreamPeerTCP> TCPServer::take_connection() { Ref<StreamPeerTCP> conn; if (!is_connection_available()) { return conn; } Ref<NetSocket> ns; - IP_Address ip; + IPAddress ip; uint16_t port = 0; ns = _sock->accept(ip, port); if (!ns.is_valid()) { @@ -110,16 +117,16 @@ Ref<StreamPeerTCP> TCP_Server::take_connection() { return conn; } -void TCP_Server::stop() { +void TCPServer::stop() { if (_sock.is_valid()) { _sock->close(); } } -TCP_Server::TCP_Server() : +TCPServer::TCPServer() : _sock(Ref<NetSocket>(NetSocket::create())) { } -TCP_Server::~TCP_Server() { +TCPServer::~TCPServer() { stop(); } diff --git a/core/io/tcp_server.h b/core/io/tcp_server.h index eb715a745c..10985a04d5 100644 --- a/core/io/tcp_server.h +++ b/core/io/tcp_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,8 +36,8 @@ #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" -class TCP_Server : public Reference { - GDCLASS(TCP_Server, Reference); +class TCPServer : public RefCounted { + GDCLASS(TCPServer, RefCounted); protected: enum { @@ -48,15 +48,16 @@ protected: static void _bind_methods(); public: - Error listen(uint16_t p_port, const IP_Address &p_bind_address = IP_Address("*")); + Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*")); + int get_local_port() const; bool is_listening() const; bool is_connection_available() const; Ref<StreamPeerTCP> take_connection(); void stop(); // Stop listening - TCP_Server(); - ~TCP_Server(); + TCPServer(); + ~TCPServer(); }; #endif // TCP_SERVER_H diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index d8ddb213c3..83d575cee8 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,9 +30,9 @@ #include "translation_loader_po.h" -#include "core/os/file_access.h" -#include "core/translation.h" -#include "core/translation_po.h" +#include "core/io/file_access.h" +#include "core/string/translation.h" +#include "core/string/translation_po.h" RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { enum Status { @@ -70,7 +70,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { is_eof = f->eof_reached(); // If we reached last line and it's not a content line, break, otherwise let processing that last loop - if (is_eof && l.empty()) { + if (is_eof && l.is_empty()) { if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) { memdelete(f); ERR_FAIL_V_MSG(RES(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line)); @@ -194,7 +194,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { l = l.substr(1, l.length()); // Find final quote, ignoring escaped ones (\"). // The escape_next logic is necessary to properly parse things like \\" - // where the blackslash is the one being escaped, not the quote. + // where the backslash is the one being escaped, not the quote. int end_pos = -1; bool escape_next = false; for (int i = 0; i < l.length(); i++) { @@ -277,7 +277,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { return translation; } -RES TranslationLoaderPO::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES TranslationLoaderPO::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_CANT_OPEN; } diff --git a/core/io/translation_loader_po.h b/core/io/translation_loader_po.h index a196a37dc0..c52820e60d 100644 --- a/core/io/translation_loader_po.h +++ b/core/io/translation_loader_po.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,14 +31,14 @@ #ifndef TRANSLATION_LOADER_PO_H #define TRANSLATION_LOADER_PO_H +#include "core/io/file_access.h" #include "core/io/resource_loader.h" -#include "core/os/file_access.h" -#include "core/translation.h" +#include "core/string/translation.h" class TranslationLoaderPO : public ResourceFormatLoader { public: static RES load_translation(FileAccess *f, Error *r_error = nullptr); - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/core/io/udp_server.cpp b/core/io/udp_server.cpp index acd15aadc6..27a1cab721 100644 --- a/core/io/udp_server.cpp +++ b/core/io/udp_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,6 +34,7 @@ void UDPServer::_bind_methods() { ClassDB::bind_method(D_METHOD("listen", "port", "bind_address"), &UDPServer::listen, DEFVAL("*")); ClassDB::bind_method(D_METHOD("poll"), &UDPServer::poll); ClassDB::bind_method(D_METHOD("is_connection_available"), &UDPServer::is_connection_available); + ClassDB::bind_method(D_METHOD("get_local_port"), &UDPServer::get_local_port); ClassDB::bind_method(D_METHOD("is_listening"), &UDPServer::is_listening); ClassDB::bind_method(D_METHOD("take_connection"), &UDPServer::take_connection); ClassDB::bind_method(D_METHOD("stop"), &UDPServer::stop); @@ -49,7 +50,7 @@ Error UDPServer::poll() { } Error err; int read; - IP_Address ip; + IPAddress ip; uint16_t port; while (true) { err = _sock->recvfrom(recv_buffer, sizeof(recv_buffer), read, ip, port); @@ -86,7 +87,7 @@ Error UDPServer::poll() { return OK; } -Error UDPServer::listen(uint16_t p_port, const IP_Address &p_bind_address) { +Error UDPServer::listen(uint16_t p_port, const IPAddress &p_bind_address) { ERR_FAIL_COND_V(!_sock.is_valid(), ERR_UNAVAILABLE); ERR_FAIL_COND_V(_sock->is_open(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER); @@ -112,11 +113,15 @@ Error UDPServer::listen(uint16_t p_port, const IP_Address &p_bind_address) { stop(); return err; } - bind_address = p_bind_address; - bind_port = p_port; return OK; } +int UDPServer::get_local_port() const { + uint16_t local_port; + _sock->get_socket_address(nullptr, &local_port); + return local_port; +} + bool UDPServer::is_listening() const { ERR_FAIL_COND_V(!_sock.is_valid(), false); @@ -162,7 +167,7 @@ Ref<PacketPeerUDP> UDPServer::take_connection() { return peer.peer; } -void UDPServer::remove_peer(IP_Address p_ip, int p_port) { +void UDPServer::remove_peer(IPAddress p_ip, int p_port) { Peer peer; peer.ip = p_ip; peer.port = p_port; @@ -176,8 +181,6 @@ void UDPServer::stop() { if (_sock.is_valid()) { _sock->close(); } - bind_port = 0; - bind_address = IP_Address(); List<Peer>::Element *E = peers.front(); while (E) { E->get().peer->disconnect_shared_socket(); diff --git a/core/io/udp_server.h b/core/io/udp_server.h index 3175b09b19..e49a559c51 100644 --- a/core/io/udp_server.h +++ b/core/io/udp_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,8 +34,8 @@ #include "core/io/net_socket.h" #include "core/io/packet_peer_udp.h" -class UDPServer : public Reference { - GDCLASS(UDPServer, Reference); +class UDPServer : public RefCounted { + GDCLASS(UDPServer, RefCounted); protected: enum { @@ -44,7 +44,7 @@ protected: struct Peer { PacketPeerUDP *peer; - IP_Address ip; + IPAddress ip; uint16_t port = 0; bool operator==(const Peer &p_other) const { @@ -53,21 +53,18 @@ protected: }; uint8_t recv_buffer[PACKET_BUFFER_SIZE]; - int bind_port = 0; - IP_Address bind_address; - List<Peer> peers; List<Peer> pending; int max_pending_connections = 16; Ref<NetSocket> _sock; - static void _bind_methods(); public: - void remove_peer(IP_Address p_ip, int p_port); - Error listen(uint16_t p_port, const IP_Address &p_bind_address = IP_Address("*")); + void remove_peer(IPAddress p_ip, int p_port); + Error listen(uint16_t p_port, const IPAddress &p_bind_address = IPAddress("*")); Error poll(); + int get_local_port() const; bool is_listening() const; bool is_connection_available() const; void set_max_pending_connections(int p_max); diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp index fc75ac7d1e..938d93a01b 100644 --- a/core/io/xml_parser.cpp +++ b/core/io/xml_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,69 +30,12 @@ #include "xml_parser.h" -#include "core/print_string.h" +#include "core/string/print_string.h" //#define DEBUG_XML VARIANT_ENUM_CAST(XMLParser::NodeType); -static bool _equalsn(const char32_t *str1, const char32_t *str2, int len) { - int i; - for (i = 0; i < len && str1[i] && str2[i]; ++i) { - if (str1[i] != str2[i]) { - return false; - } - } - - // if one (or both) of the strings was smaller then they - // are only equal if they have the same length - return (i == len) || (str1[i] == 0 && str2[i] == 0); -} - -String XMLParser::_replace_special_characters(const String &origstr) { - int pos = origstr.find("&"); - int oldPos = 0; - - if (pos == -1) { - return origstr; - } - - String newstr; - - while (pos != -1 && pos < origstr.length() - 2) { - // check if it is one of the special characters - - int specialChar = -1; - for (int i = 0; i < (int)special_characters.size(); ++i) { - const char32_t *p = &origstr[pos] + 1; - - if (_equalsn(&special_characters[i][1], p, special_characters[i].length() - 1)) { - specialChar = i; - break; - } - } - - if (specialChar != -1) { - newstr += (origstr.substr(oldPos, pos - oldPos)); - newstr += (special_characters[specialChar][0]); - pos += special_characters[specialChar].length(); - } else { - newstr += (origstr.substr(oldPos, pos - oldPos + 1)); - pos += 1; - } - - // find next & - oldPos = pos; - pos = origstr.find("&", pos); - } - - if (oldPos < origstr.length() - 1) { - newstr += (origstr.substr(oldPos, origstr.length() - oldPos)); - } - - return newstr; -} - static inline bool _is_white_space(char c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); } @@ -116,7 +59,7 @@ bool XMLParser::_set_text(char *start, char *end) { // set current text to the parsed text, and replace xml special characters String s = String::utf8(start, (int)(end - start)); - node_name = _replace_special_characters(s); + node_name = s.xml_unescape(); // current XML node type is text node_type = NODE_TEXT; @@ -132,7 +75,7 @@ void XMLParser::_parse_closing_xml_element() { ++P; const char *pBeginClose = P; - while (*P != '>') { + while (*P && *P != '>') { ++P; } @@ -140,7 +83,10 @@ void XMLParser::_parse_closing_xml_element() { #ifdef DEBUG_XML print_line("XML CLOSE: " + node_name); #endif - ++P; + + if (*P) { + ++P; + } } void XMLParser::_ignore_definition() { @@ -148,11 +94,14 @@ void XMLParser::_ignore_definition() { char *F = P; // move until end marked with '>' reached - while (*P != '>') { + while (*P && *P != '>') { ++P; } node_name.parse_utf8(F, P - F); - ++P; + + if (*P) { + ++P; + } } bool XMLParser::_parse_cdata() { @@ -170,6 +119,7 @@ bool XMLParser::_parse_cdata() { } if (!*P) { + node_name = ""; return true; } @@ -188,10 +138,9 @@ bool XMLParser::_parse_cdata() { } if (cDataEnd) { - node_name = String::utf8(cDataBegin, (int)(cDataEnd - cDataBegin)); - } else { - node_name = ""; + cDataEnd = P; } + node_name = String::utf8(cDataBegin, (int)(cDataEnd - cDataBegin)); #ifdef DEBUG_XML print_line("XML CDATA: " + node_name); #endif @@ -203,24 +152,45 @@ void XMLParser::_parse_comment() { node_type = NODE_COMMENT; P += 1; - char *pCommentBegin = P; - - int count = 1; + char *pEndOfInput = data + length; + char *pCommentBegin; + char *pCommentEnd; - // move until end of comment reached - while (count) { - if (*P == '>') { - --count; - } else if (*P == '<') { - ++count; + if (P + 1 < pEndOfInput && P[0] == '-' && P[1] == '-') { + // Comment, use '-->' as end. + pCommentBegin = P + 2; + for (pCommentEnd = pCommentBegin; pCommentEnd + 2 < pEndOfInput; pCommentEnd++) { + if (pCommentEnd[0] == '-' && pCommentEnd[1] == '-' && pCommentEnd[2] == '>') { + break; + } + } + if (pCommentEnd + 2 < pEndOfInput) { + P = pCommentEnd + 3; + } else { + P = pCommentEnd = pEndOfInput; + } + } else { + // Like document type definition, match angle brackets. + pCommentBegin = P; + + int count = 1; + while (*P && count) { + if (*P == '>') { + --count; + } else if (*P == '<') { + ++count; + } + ++P; } - ++P; + if (count) { + pCommentEnd = P; + } else { + pCommentEnd = P - 1; + } } - P -= 3; - node_name = String::utf8(pCommentBegin + 2, (int)(P - pCommentBegin - 2)); - P += 3; + node_name = String::utf8(pCommentBegin, (int)(pCommentEnd - pCommentBegin)); #ifdef DEBUG_XML print_line("XML COMMENT: " + node_name); #endif @@ -235,14 +205,14 @@ void XMLParser::_parse_opening_xml_element() { const char *startName = P; // find end of element - while (*P != '>' && !_is_white_space(*P)) { + while (*P && *P != '>' && !_is_white_space(*P)) { ++P; } const char *endName = P; // find attributes - while (*P != '>') { + while (*P && *P != '>') { if (_is_white_space(*P)) { ++P; } else { @@ -252,10 +222,14 @@ void XMLParser::_parse_opening_xml_element() { // read the attribute names const char *attributeNameBegin = P; - while (!_is_white_space(*P) && *P != '=') { + while (*P && !_is_white_space(*P) && *P != '=') { ++P; } + if (!*P) { + break; + } + const char *attributeNameEnd = P; ++P; @@ -266,7 +240,7 @@ void XMLParser::_parse_opening_xml_element() { } if (!*P) { // malformatted xml file - return; + break; } const char attributeQuoteChar = *P; @@ -278,12 +252,10 @@ void XMLParser::_parse_opening_xml_element() { ++P; } - if (!*P) { // malformatted xml file - return; - } - const char *attributeValueEnd = P; - ++P; + if (*P) { + ++P; + } Attribute attr; attr.name = String::utf8(attributeNameBegin, @@ -292,7 +264,7 @@ void XMLParser::_parse_opening_xml_element() { String s = String::utf8(attributeValueBegin, (int)(attributeValueEnd - attributeValueBegin)); - attr.value = _replace_special_characters(s); + attr.value = s.xml_unescape(); attributes.push_back(attr); } else { // tag is closed directly @@ -315,7 +287,9 @@ void XMLParser::_parse_opening_xml_element() { print_line("XML OPEN: " + node_name); #endif - ++P; + if (*P) { + ++P; + } } void XMLParser::_parse_current_node() { @@ -327,10 +301,6 @@ void XMLParser::_parse_current_node() { ++P; } - if (!*P) { - return; - } - if (P - start > 0) { // we found some text, store it if (_set_text(start, P)) { @@ -338,6 +308,10 @@ void XMLParser::_parse_current_node() { } } + if (!*P) { + return; + } + ++P; // based on current token, parse and report next element @@ -401,7 +375,7 @@ void XMLParser::_bind_methods() { } Error XMLParser::read() { - // if not end reached, parse the node + // if end not reached, parse the node if (P && (P - data) < (int64_t)length - 1 && *P != 0) { _parse_current_node(); return OK; @@ -490,7 +464,7 @@ Error XMLParser::open_buffer(const Vector<uint8_t> &p_buffer) { length = p_buffer.size(); data = memnew_arr(char, length + 1); - copymem(data, p_buffer.ptr(), length); + memcpy(data, p_buffer.ptr(), length); data[length] = 0; P = data; return OK; @@ -502,7 +476,7 @@ Error XMLParser::open(const String &p_path) { ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'."); - length = file->get_len(); + length = file->get_length(); ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT); if (data) { @@ -555,11 +529,6 @@ int XMLParser::get_current_line() const { } XMLParser::XMLParser() { - special_characters.push_back("&"); - special_characters.push_back("<lt;"); - special_characters.push_back(">gt;"); - special_characters.push_back("\"quot;"); - special_characters.push_back("'apos;"); } XMLParser::~XMLParser() { diff --git a/core/io/xml_parser.h b/core/io/xml_parser.h index ee2174d52c..1113cce715 100644 --- a/core/io/xml_parser.h +++ b/core/io/xml_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,17 +31,17 @@ #ifndef XML_PARSER_H #define XML_PARSER_H -#include "core/os/file_access.h" -#include "core/reference.h" -#include "core/ustring.h" -#include "core/vector.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" /* Based on irrXML (see their zlib license). Added mainly for compatibility with their Collada loader. */ -class XMLParser : public Reference { - GDCLASS(XMLParser, Reference); +class XMLParser : public RefCounted { + GDCLASS(XMLParser, RefCounted); public: //! Enumeration of all supported source text file formats @@ -68,8 +68,6 @@ private: char *data = nullptr; char *P = nullptr; uint64_t length = 0; - void unescape(String &p_str); - Vector<String> special_characters; String node_name; bool node_empty = false; NodeType node_type = NODE_NONE; @@ -82,7 +80,6 @@ private: Vector<Attribute> attributes; - String _replace_special_characters(const String &origstr); bool _set_text(char *start, char *end); void _parse_closing_xml_element(); void _ignore_definition(); diff --git a/core/io/zip_io.cpp b/core/io/zip_io.cpp index b8e7fd34d0..fb4c76aa7a 100644 --- a/core/io/zip_io.cpp +++ b/core/io/zip_io.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,8 +30,6 @@ #include "zip_io.h" -#include "core/os/copymem.h" - void *zipio_open(void *data, const char *p_fname, int mode) { FileAccess *&f = *(FileAccess **)data; @@ -70,13 +68,13 @@ long zipio_tell(voidpf opaque, voidpf stream) { long zipio_seek(voidpf opaque, voidpf stream, uLong offset, int origin) { FileAccess *f = *(FileAccess **)opaque; - int pos = offset; + uint64_t pos = offset; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR: pos = f->get_position() + offset; break; case ZLIB_FILEFUNC_SEEK_END: - pos = f->get_len() + offset; + pos = f->get_length() + offset; break; default: break; @@ -103,7 +101,7 @@ int zipio_testerror(voidpf opaque, voidpf stream) { voidpf zipio_alloc(voidpf opaque, uInt items, uInt size) { voidpf ptr = memalloc(items * size); - zeromem(ptr, items * size); + memset(ptr, 0, items * size); return ptr; } diff --git a/core/io/zip_io.h b/core/io/zip_io.h index ba2065b5d1..776473bfa1 100644 --- a/core/io/zip_io.h +++ b/core/io/zip_io.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,7 @@ #ifndef ZIP_IO_H #define ZIP_IO_H -#include "core/os/file_access.h" +#include "core/io/file_access.h" // Not directly used in this header, but assumed available in downstream users // like platform/*/export/export.cpp. Could be fixed, but probably better to have |