feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
@ -98,6 +99,8 @@ if env["builtin_zlib"]:
env.Prepend(CPPPATH=[thirdparty_zlib_dir])
if env.dev_build:
env_thirdparty.Append(CPPDEFINES=["ZLIB_DEBUG"])
# Affects headers so it should also be defined for Godot code
env.Append(CPPDEFINES=["ZLIB_DEBUG"])
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_zlib_sources)
@ -140,7 +143,7 @@ if env["builtin_zstd"]:
"decompress/zstd_decompress_block.c",
"decompress/zstd_decompress.c",
]
if env["platform"] in ["android", "ios", "linuxbsd", "macos"]:
if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64":
# Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h
thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S")
thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources]

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -36,6 +36,7 @@
#include "core/license.gen.h"
#include "core/variant/typed_array.h"
#include "core/version.h"
#include "servers/rendering/rendering_device.h"
void Engine::set_physics_ticks_per_second(int p_ips) {
ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0.");
@ -68,6 +69,11 @@ double Engine::get_physics_jitter_fix() const {
void Engine::set_max_fps(int p_fps) {
_max_fps = p_fps > 0 ? p_fps : 0;
RenderingDevice *rd = RenderingDevice::get_singleton();
if (rd) {
rd->_set_max_fps(_max_fps);
}
}
int Engine::get_max_fps() const {
@ -110,6 +116,10 @@ void Engine::set_time_scale(double p_scale) {
}
double Engine::get_time_scale() const {
return freeze_time_scale ? 0 : _time_scale;
}
double Engine::get_unfrozen_time_scale() const {
return _time_scale;
}
@ -238,6 +248,9 @@ String Engine::get_architecture_name() const {
return "ppc";
#endif
#elif defined(__loongarch64)
return "loongarch64";
#elif defined(__wasm__)
#if defined(__wasm64__)
return "wasm64";
@ -263,6 +276,24 @@ bool Engine::is_generate_spirv_debug_info_enabled() const {
return generate_spirv_debug_info;
}
bool Engine::is_extra_gpu_memory_tracking_enabled() const {
return extra_gpu_memory_tracking;
}
#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
bool Engine::is_accurate_breadcrumbs_enabled() const {
return accurate_breadcrumbs;
}
#endif
void Engine::set_print_to_stdout(bool p_enabled) {
CoreGlobals::print_line_enabled = p_enabled;
}
bool Engine::is_printing_to_stdout() const {
return CoreGlobals::print_line_enabled;
}
void Engine::set_print_error_messages(bool p_enabled) {
CoreGlobals::print_error_enabled = p_enabled;
}
@ -380,6 +411,18 @@ bool Engine::notify_frame_server_synced() {
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
}
void Engine::set_freeze_time_scale(bool p_frozen) {
freeze_time_scale = p_frozen;
}
void Engine::set_embedded_in_editor(bool p_enabled) {
embedded_in_editor = p_enabled;
}
bool Engine::is_embedded_in_editor() const {
return embedded_in_editor;
}
Engine::Engine() {
singleton = this;
}

View file

@ -34,7 +34,6 @@
#include "core/os/main_loop.h"
#include "core/string/ustring.h"
#include "core/templates/list.h"
#include "core/templates/vector.h"
template <typename T>
class TypedArray;
@ -72,6 +71,10 @@ private:
bool abort_on_gpu_errors = false;
bool use_validation_layers = false;
bool generate_spirv_debug_info = false;
bool extra_gpu_memory_tracking = false;
#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
bool accurate_breadcrumbs = false;
#endif
int32_t gpu_idx = -1;
uint64_t _process_frames = 0;
@ -83,6 +86,8 @@ private:
bool editor_hint = false;
bool project_manager_hint = false;
bool extension_reloading = false;
bool embedded_in_editor = false;
bool recovery_mode_hint = false;
bool _print_header = true;
@ -95,6 +100,8 @@ private:
int server_syncs = 0;
bool frame_server_synced = false;
bool freeze_time_scale = false;
public:
static Engine *get_singleton();
@ -126,6 +133,10 @@ public:
void set_time_scale(double p_scale);
double get_time_scale() const;
double get_unfrozen_time_scale() const;
void set_print_to_stdout(bool p_enabled);
bool is_printing_to_stdout() const;
void set_print_error_messages(bool p_enabled);
bool is_printing_error_messages() const;
@ -152,6 +163,9 @@ public:
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) { extension_reloading = p_enabled; }
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return extension_reloading; }
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) { recovery_mode_hint = p_enabled; }
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return recovery_mode_hint; }
#else
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_editor_hint() const { return false; }
@ -161,6 +175,9 @@ public:
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) {}
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return false; }
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return false; }
#endif
Dictionary get_version_info() const;
@ -181,11 +198,19 @@ public:
bool is_abort_on_gpu_errors_enabled() const;
bool is_validation_layers_enabled() const;
bool is_generate_spirv_debug_info_enabled() const;
bool is_extra_gpu_memory_tracking_enabled() const;
#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
bool is_accurate_breadcrumbs_enabled() const;
#endif
int32_t get_gpu_index() const;
void increment_frames_drawn();
bool notify_frame_server_synced();
void set_freeze_time_scale(bool p_frozen);
void set_embedded_in_editor(bool p_enabled);
bool is_embedded_in_editor() const;
Engine();
virtual ~Engine();
};

View file

@ -39,7 +39,6 @@
#include "core/io/marshalls.h"
#include "core/io/resource_uid.h"
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/templates/rb_set.h"
#include "core/variant/typed_array.h"
#include "core/variant/variant_parser.h"
@ -194,7 +193,7 @@ String ProjectSettings::localize_path(const String &p_path) const {
return cwd.replace_first(res_path, "res://");
} else {
int sep = path.rfind("/");
int sep = path.rfind_char('/');
if (sep == -1) {
return "res://" + path;
}
@ -214,36 +213,36 @@ String ProjectSettings::localize_path(const String &p_path) const {
}
void ProjectSettings::set_initial_value(const String &p_name, const Variant &p_value) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
// Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
props[p_name].initial = p_value.duplicate();
}
void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restart) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].restart_if_changed = p_restart;
}
void ProjectSettings::set_as_basic(const String &p_name, bool p_basic) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].basic = p_basic;
}
void ProjectSettings::set_as_internal(const String &p_name, bool p_internal) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].internal = p_internal;
}
void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
props[p_name].ignore_value_in_docs = p_ignore;
#endif
}
bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_V_MSG(!props.has(p_name), false, vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
return props[p_name].ignore_value_in_docs;
#else
@ -262,6 +261,12 @@ String ProjectSettings::globalize_path(const String &p_path) const {
return p_path.replace("res:/", resource_path);
}
return p_path.replace("res://", "");
} else if (p_path.begins_with("uid://")) {
const String path = ResourceUID::uid_to_path(p_path);
if (!resource_path.is_empty()) {
return path.replace("res:/", resource_path);
}
return path.replace("res://", "");
} else if (p_path.begins_with("user://")) {
String data_dir = OS::get_singleton()->get_user_data_dir();
if (!data_dir.is_empty()) {
@ -300,7 +305,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
}
{ // Feature overrides.
int dot = p_name.operator String().find(".");
int dot = p_name.operator String().find_char('.');
if (dot != -1) {
Vector<String> s = p_name.operator String().split(".");
@ -348,7 +353,6 @@ bool ProjectSettings::_get(const StringName &p_name, Variant &r_ret) const {
_THREAD_SAFE_METHOD_
if (!props.has(p_name)) {
WARN_PRINT("Property not found: " + String(p_name));
return false;
}
r_ret = props[p_name].variant;
@ -372,7 +376,7 @@ Variant ProjectSettings::get_setting_with_override(const StringName &p_name) con
}
if (!props.has(name)) {
WARN_PRINT("Property not found: " + String(name));
WARN_PRINT(vformat("Property not found: '%s'.", String(name)));
return Variant();
}
return props[name].variant;
@ -436,7 +440,7 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const {
for (const _VCSort &E : vclist) {
String prop_info_name = E.name;
int dot = prop_info_name.find(".");
int dot = prop_info_name.find_char('.');
if (dot != -1 && !custom_prop_info.has(prop_info_name)) {
prop_info_name = prop_info_name.substr(0, dot);
}
@ -468,13 +472,30 @@ void ProjectSettings::_emit_changed() {
emit_signal("settings_changed");
}
bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) {
bool ProjectSettings::load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) {
return ProjectSettings::_load_resource_pack(p_pack, p_replace_files, p_offset, false);
}
bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset, bool p_main_pack) {
if (PackedData::get_singleton()->is_disabled()) {
return false;
}
bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK;
if (p_pack == "res://") {
// Loading the resource directory as a pack source is reserved for internal use only.
return false;
}
if (!p_main_pack && !using_datapack && !OS::get_singleton()->get_resource_dir().is_empty()) {
// Add the project's resource file system to PackedData so directory access keeps working when
// the game is running without a main pack, like in the editor or on Android.
PackedData::get_singleton()->add_pack_source(memnew(PackedSourceDirectory));
PackedData::get_singleton()->add_pack("res://", false, 0);
DirAccess::make_default<DirAccessPack>(DirAccess::ACCESS_RESOURCES);
using_datapack = true;
}
bool ok = PackedData::get_singleton()->add_pack(p_pack, p_replace_files, p_offset) == OK;
if (!ok) {
return false;
}
@ -487,14 +508,17 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f
ResourceUID::get_singleton()->load_from_cache(false);
}
//if data.pck is found, all directory access will be from here
DirAccess::make_default<DirAccessPack>(DirAccess::ACCESS_RESOURCES);
using_datapack = true;
// If the data pack was found, all directory access will be from here.
if (!using_datapack) {
DirAccess::make_default<DirAccessPack>(DirAccess::ACCESS_RESOURCES);
using_datapack = true;
}
return true;
}
void ProjectSettings::_convert_to_last_version(int p_from_version) {
#ifndef DISABLE_DEPRECATED
if (p_from_version <= 3) {
// Converts the actions from array to dictionary (array of events to dictionary with deadzone + events)
for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) {
@ -508,6 +532,7 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
}
}
}
#endif // DISABLE_DEPRECATED
}
/*
@ -548,8 +573,8 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
// Attempt with a user-defined main pack first
if (!p_main_pack.is_empty()) {
bool ok = _load_resource_pack(p_main_pack);
ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, "Cannot open resource pack '" + p_main_pack + "'.");
bool ok = _load_resource_pack(p_main_pack, false, 0, true);
ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack));
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK && !p_ignore_override) {
@ -567,7 +592,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
// and if so, we attempt loading it at the end.
// Attempt with PCK bundled into executable.
bool found = _load_resource_pack(exec_path);
bool found = _load_resource_pack(exec_path, false, 0, true);
// Attempt with exec_name.pck.
// (This is the usual case when distributing a Godot game.)
@ -583,20 +608,20 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
#ifdef MACOS_ENABLED
if (!found) {
// Attempt to load PCK from macOS .app bundle resources.
found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck"));
found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck"), false, 0, true);
}
#endif
if (!found) {
// Try to load data pack at the location of the executable.
// As mentioned above, we have two potential names to attempt.
found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck")) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck"));
found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck"), false, 0, true) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck"), false, 0, true);
}
if (!found) {
// If we couldn't find them next to the executable, we attempt
// the current working directory. Same story, two tests.
found = _load_resource_pack(exec_basename + ".pck") || _load_resource_pack(exec_filename + ".pck");
found = _load_resource_pack(exec_basename + ".pck", false, 0, true) || _load_resource_pack(exec_filename + ".pck", false, 0, true);
}
// If we opened our package, try and load our project.
@ -624,11 +649,33 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
return err;
}
#ifdef MACOS_ENABLED
// Attempt to load project file from macOS .app bundle resources.
resource_path = OS::get_singleton()->get_bundle_resource_dir();
if (!resource_path.is_empty()) {
if (resource_path[resource_path.length() - 1] == '/') {
resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end.
}
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", resource_path));
d->change_dir(resource_path);
Error err;
err = _load_settings_text_or_binary(resource_path.path_join("project.godot"), resource_path.path_join("project.binary"));
if (err == OK && !p_ignore_override) {
// Optional, we don't mind if it fails.
_load_settings_text(resource_path.path_join("override.cfg"));
return err;
}
}
#endif
// Nothing was found, try to find a project file in provided path (`p_path`)
// or, if requested (`p_upwards`) in parent directories.
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_path + "'.");
ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_path));
d->change_dir(p_path);
String current_dir = d->get_current_dir();
@ -724,7 +771,7 @@ Error ProjectSettings::_load_settings_binary(const String &p_path) {
cs[slen] = 0;
f->get_buffer((uint8_t *)cs.ptr(), slen);
String key;
key.parse_utf8(cs.ptr());
key.parse_utf8(cs.ptr(), slen);
uint32_t vlen = f->get_32();
Vector<uint8_t> d;
@ -732,7 +779,7 @@ Error ProjectSettings::_load_settings_binary(const String &p_path) {
f->get_buffer(d.ptrw(), vlen);
Variant value;
err = decode_variant(value, d.ptr(), d.size(), nullptr, true);
ERR_CONTINUE_MSG(err != OK, "Error decoding property: " + key + ".");
ERR_CONTINUE_MSG(err != OK, vformat("Error decoding property: '%s'.", key));
set(key, value);
}
@ -774,7 +821,7 @@ Error ProjectSettings::_load_settings_text(const String &p_path) {
last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));
return OK;
}
ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing " + p_path + " at line " + itos(lines) + ": " + error_text + " File might be corrupted.");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing '%s' at line %d: %s File might be corrupted.", p_path, lines, error_text));
if (!assign.is_empty()) {
if (section.is_empty() && assign == "config_version") {
@ -800,7 +847,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
// If the file exists but can't be loaded, we want to know it.
ERR_PRINT("Couldn't load file '" + p_bin_path + "', error code " + itos(err) + ".");
ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_bin_path, err));
}
// Fallback to text-based project.godot file if binary was not found.
@ -808,7 +855,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
if (err == OK) {
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
ERR_PRINT("Couldn't load file '" + p_text_path + "', error code " + itos(err) + ".");
ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_text_path, err));
}
return err;
@ -822,17 +869,17 @@ Error ProjectSettings::load_custom(const String &p_path) {
}
int ProjectSettings::get_order(const String &p_name) const {
ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order;
}
void ProjectSettings::set_order(const String &p_name, int p_order) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].order = p_order;
}
void ProjectSettings::set_builtin_order(const String &p_name) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
if (props[p_name].order >= NO_BUILTIN_ORDER_BASE) {
props[p_name].order = last_builtin_order++;
}
@ -840,12 +887,12 @@ void ProjectSettings::set_builtin_order(const String &p_name) {
bool ProjectSettings::is_builtin_setting(const String &p_name) const {
// Return true because a false negative is worse than a false positive.
ERR_FAIL_COND_V_MSG(!props.has(p_name), true, "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_V_MSG(!props.has(p_name), true, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order < NO_BUILTIN_ORDER_BASE;
}
void ProjectSettings::clear(const String &p_name) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props.erase(p_name);
}
@ -860,7 +907,7 @@ Error ProjectSettings::save() {
Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<String, List<String>> &p_props, const CustomMap &p_custom, const String &p_custom_features) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.binary at " + p_file + ".");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.binary at '%s'.", p_file));
uint8_t hdr[4] = { 'E', 'C', 'F', 'G' };
file->store_buffer(hdr, 4);
@ -873,7 +920,7 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
if (!p_custom_features.is_empty()) {
// Store how many properties are saved, add one for custom features, which must always go first.
file->store_32(count + 1);
file->store_32(uint32_t(count + 1));
String key = CoreStringName(_custom_features);
file->store_pascal_string(key);
@ -886,12 +933,12 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
err = encode_variant(p_custom_features, buff.ptrw(), len, false);
ERR_FAIL_COND_V(err != OK, err);
file->store_32(len);
file->store_32(uint32_t(len));
file->store_buffer(buff.ptr(), buff.size());
} else {
// Store how many properties are saved.
file->store_32(count);
file->store_32(uint32_t(count));
}
for (const KeyValue<String, List<String>> &E : p_props) {
@ -918,7 +965,7 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S
err = encode_variant(value, buff.ptrw(), len, true);
ERR_FAIL_COND_V_MSG(err != OK, ERR_INVALID_DATA, "Error when trying to encode Variant.");
file->store_32(len);
file->store_32(uint32_t(len));
file->store_buffer(buff.ptr(), buff.size());
}
}
@ -930,7 +977,7 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.godot - " + p_file + ".");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.godot - %s.", p_file));
file->store_line("; Engine configuration file.");
file->store_line("; It's best edited using the editor UI and not directly,");
@ -1016,7 +1063,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
// Check for the existence of a csproj file.
if (_csproj_exists(p_path.get_base_dir())) {
if (_csproj_exists(get_resource_path())) {
// If there is a csproj file, add the C# feature if it doesn't already exist.
if (!project_features.has("C#")) {
project_features.append("C#");
@ -1076,7 +1123,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
String category = E.name;
String name = E.name;
int div = category.find("/");
int div = category.find_char('/');
if (div < 0) {
category = "";
@ -1103,7 +1150,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
} else if (p_path.ends_with(".binary")) {
return _save_settings_binary(p_path, save_props, p_custom, save_features);
} else {
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown config file format: " + p_path);
ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unknown config file format: '%s'.", p_path));
}
}
@ -1168,22 +1215,16 @@ bool ProjectSettings::is_project_loaded() const {
}
bool ProjectSettings::_property_can_revert(const StringName &p_name) const {
if (!props.has(p_name)) {
return false;
}
return props[p_name].initial != props[p_name].variant;
return props.has(p_name);
}
bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
if (!props.has(p_name)) {
return false;
const RBMap<StringName, ProjectSettings::VariantContainer>::Element *value = props.find(p_name);
if (value) {
r_property = value->value().initial.duplicate();
return true;
}
// Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
r_property = props[p_name].initial.duplicate();
return true;
return false;
}
void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) {
@ -1204,10 +1245,10 @@ void ProjectSettings::refresh_global_class_list() {
Array script_classes = get_global_class_list();
for (int i = 0; i < script_classes.size(); i++) {
Dictionary c = script_classes[i];
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
continue;
}
ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]);
ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
}
}
@ -1378,7 +1419,7 @@ void ProjectSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("localize_path", "path"), &ProjectSettings::localize_path);
ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path);
ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save);
ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0));
ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::load_resource_pack, DEFVAL(true), DEFVAL(0));
ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);
@ -1398,7 +1439,7 @@ void ProjectSettings::_add_builtin_input_map() {
}
Dictionary action;
action["deadzone"] = Variant(0.5f);
action["deadzone"] = Variant(InputMap::DEFAULT_DEADZONE);
action["events"] = events;
String action_name = "input/" + E.key;
@ -1467,15 +1508,12 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/transparent", false);
GLOBAL_DEF("display/window/size/extend_to_title", false);
GLOBAL_DEF("display/window/size/no_focus", false);
GLOBAL_DEF("display/window/size/sharp_corners", false);
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
#ifdef TOOLS_ENABLED
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false);
#endif
GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true);
GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true);
@ -1489,15 +1527,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0);
GLOBAL_DEF("audio/general/ios/mix_with_others", false);
PackedStringArray extensions;
extensions.push_back("gd");
if (ClassDB::class_exists("CSharpScript")) {
extensions.push_back("cs");
}
extensions.push_back("gdshader");
GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions);
_add_builtin_input_map();
// Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum.
@ -1505,7 +1534,15 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/subwindows/embed_subwindows", true);
// Keep the enum values in sync with the `DisplayServer::VSyncMode` enum.
custom_prop_info["display/window/vsync/vsync_mode"] = PropertyInfo(Variant::INT, "display/window/vsync/vsync_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Adaptive,Mailbox");
custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded");
GLOBAL_DEF("display/window/frame_pacing/android/enable_frame_pacing", true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/frame_pacing/android/swappy_mode", PROPERTY_HINT_ENUM, "pipeline_forced_on,auto_fps_pipeline_forced_on,auto_fps_auto_pipeline"), 2);
#ifdef DISABLE_DEPRECATED
custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Safe:1,Separate");
#else
custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Unsafe (deprecated),Safe,Separate");
#endif
GLOBAL_DEF("physics/2d/run_on_separate_thread", false);
GLOBAL_DEF("physics/3d/run_on_separate_thread", false);
@ -1548,6 +1585,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/block_size_kb", PROPERTY_HINT_RANGE, "4,2048,1,or_greater"), 256);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/max_size_mb", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 128);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/texture_upload_region_size_px", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/texture_download_region_size_px", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
GLOBAL_DEF_RST(PropertyInfo(Variant::BOOL, "rendering/rendering_device/pipeline_cache/enable"), true);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/rendering_device/pipeline_cache/save_chunk_size_mb", PROPERTY_HINT_RANGE, "0.000001,64.0,0.001,or_greater"), 3.0);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/vulkan/max_descriptors_per_pool", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
@ -1563,13 +1601,18 @@ ProjectSettings::ProjectSettings() {
// installed by the scripts provided in the repository
// (check `misc/scripts/install_d3d12_sdk_windows.py`).
// For example, if the script installs 1.613.3, the default value must be 613.
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version"), 613);
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version", PROPERTY_HINT_RANGE, "0,10000,1,or_greater,hide_slider"), 613);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror"), 0);
GLOBAL_DEF("collada/use_ambient", false);
// Input settings
GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false);
GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1);
// These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix().
GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray());
GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray());
@ -1582,6 +1625,7 @@ ProjectSettings::ProjectSettings() {
ProjectSettings::ProjectSettings(const String &p_path) {
if (load_custom(p_path) == OK) {
resource_path = p_path.get_base_dir();
project_loaded = true;
}
}

View file

@ -47,10 +47,8 @@ public:
typedef HashMap<String, Variant> CustomMap;
static const String PROJECT_DATA_DIR_NAME_SUFFIX;
enum {
// Properties that are not for built in values begin from this value, so builtin ones are displayed first.
NO_BUILTIN_ORDER_BASE = 1 << 16
};
// Properties that are not for built in values begin from this value, so builtin ones are displayed first.
constexpr static const int32_t NO_BUILTIN_ORDER_BASE = 1 << 16;
#ifdef TOOLS_ENABLED
const static PackedStringArray get_required_features();
@ -137,7 +135,8 @@ protected:
void _convert_to_last_version(int p_from_version);
bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0);
bool load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset);
bool _load_resource_pack(const String &p_pack, bool p_replace_files = true, int p_offset = 0, bool p_main_pack = false);
void _add_property_info_bind(const Dictionary &p_info);

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* core_string_names.cpp */
/* core_bind.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,48 +28,35 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "core_string_names.h"
#ifndef DISABLE_DEPRECATED
CoreStringNames *CoreStringNames::singleton = nullptr;
namespace core_bind {
CoreStringNames::CoreStringNames() :
free_(StaticCString::create("free")),
changed(StaticCString::create("changed")),
script(StaticCString::create("script")),
script_changed(StaticCString::create("script_changed")),
_iter_init(StaticCString::create("_iter_init")),
_iter_next(StaticCString::create("_iter_next")),
_iter_get(StaticCString::create("_iter_get")),
get_rid(StaticCString::create("get_rid")),
_to_string(StaticCString::create("_to_string")),
_custom_features(StaticCString::create("_custom_features")),
// Semaphore
x(StaticCString::create("x")),
y(StaticCString::create("y")),
z(StaticCString::create("z")),
w(StaticCString::create("w")),
r(StaticCString::create("r")),
g(StaticCString::create("g")),
b(StaticCString::create("b")),
a(StaticCString::create("a")),
position(StaticCString::create("position")),
size(StaticCString::create("size")),
end(StaticCString::create("end")),
basis(StaticCString::create("basis")),
origin(StaticCString::create("origin")),
normal(StaticCString::create("normal")),
d(StaticCString::create("d")),
h(StaticCString::create("h")),
s(StaticCString::create("s")),
v(StaticCString::create("v")),
r8(StaticCString::create("r8")),
g8(StaticCString::create("g8")),
b8(StaticCString::create("b8")),
a8(StaticCString::create("a8")),
call(StaticCString::create("call")),
call_deferred(StaticCString::create("call_deferred")),
bind(StaticCString::create("bind")),
notification(StaticCString::create("notification")),
property_list_changed(StaticCString::create("property_list_changed")) {
void Semaphore::_post_bind_compat_93605() {
post(1);
}
void Semaphore::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605);
}
// OS
String OS::_read_string_from_stdin_bind_compat_91201() {
return read_string_from_stdin(1024);
}
Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) {
return execute_with_pipe(p_path, p_arguments, true);
}
void OS::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("read_string_from_stdin"), &OS::_read_string_from_stdin_bind_compat_91201);
ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434);
}
} // namespace core_bind
#endif // DISABLE_DEPRECATED

View file

@ -29,13 +29,12 @@
/**************************************************************************/
#include "core_bind.h"
#include "core_bind.compat.inc"
#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/io/file_access_compressed.h"
#include "core/io/file_access_encrypted.h"
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/math/geometry_3d.h"
@ -56,8 +55,11 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String &
ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) {
float progress = 0;
::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress);
r_progress.resize(1);
r_progress[0] = progress;
// Default array should never be modified, it causes the hash of the method to change.
if (!ClassDB::is_default_array_arg(r_progress)) {
r_progress.resize(1);
r_progress[0] = progress;
}
return (ThreadLoadStatus)tls;
}
@ -71,7 +73,7 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
Error err = OK;
Ref<Resource> ret = ::ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err);
ERR_FAIL_COND_V_MSG(err != OK, ret, "Error loading resource: '" + p_path + "'.");
ERR_FAIL_COND_V_MSG(err != OK, ret, vformat("Error loading resource: '%s'.", p_path));
return ret;
}
@ -111,10 +113,15 @@ PackedStringArray ResourceLoader::get_dependencies(const String &p_path) {
}
bool ResourceLoader::has_cached(const String &p_path) {
String local_path = ProjectSettings::get_singleton()->localize_path(p_path);
String local_path = ::ResourceLoader::_validate_local_path(p_path);
return ResourceCache::has(local_path);
}
Ref<Resource> ResourceLoader::get_cached_ref(const String &p_path) {
String local_path = ::ResourceLoader::_validate_local_path(p_path);
return ResourceCache::get_ref(local_path);
}
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
return ::ResourceLoader::exists(p_path, p_type_hint);
}
@ -123,9 +130,13 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
return ::ResourceLoader::get_resource_uid(p_path);
}
Vector<String> ResourceLoader::list_directory(const String &p_directory) {
return ::ResourceLoader::list_directory(p_directory);
}
void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE));
ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY);
ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get);
ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE));
@ -135,8 +146,10 @@ void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &ResourceLoader::set_abort_on_missing_resources);
ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &ResourceLoader::get_dependencies);
ClassDB::bind_method(D_METHOD("has_cached", "path"), &ResourceLoader::has_cached);
ClassDB::bind_method(D_METHOD("get_cached_ref", "path"), &ResourceLoader::get_cached_ref);
ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid);
ClassDB::bind_method(D_METHOD("list_directory", "directory_path"), &ResourceLoader::list_directory);
BIND_ENUM_CONSTANT(THREAD_LOAD_INVALID_RESOURCE);
BIND_ENUM_CONSTANT(THREAD_LOAD_IN_PROGRESS);
@ -174,6 +187,10 @@ void ResourceSaver::remove_resource_format_saver(Ref<ResourceFormatSaver> p_form
::ResourceSaver::remove_resource_format_saver(p_format_saver);
}
ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) {
return ::ResourceSaver::get_resource_id_for_path(p_path, p_generate);
}
ResourceSaver *ResourceSaver::singleton = nullptr;
void ResourceSaver::_bind_methods() {
@ -181,6 +198,7 @@ void ResourceSaver::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_recognized_extensions", "type"), &ResourceSaver::get_recognized_extensions);
ClassDB::bind_method(D_METHOD("add_resource_format_saver", "format_saver", "at_front"), &ResourceSaver::add_resource_format_saver, DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_resource_format_saver", "format_saver"), &ResourceSaver::remove_resource_format_saver);
ClassDB::bind_method(D_METHOD("get_resource_id_for_path", "path", "generate"), &ResourceSaver::get_resource_id_for_path, DEFVAL(false));
BIND_BITFIELD_FLAG(FLAG_NONE);
BIND_BITFIELD_FLAG(FLAG_RELATIVE_PATHS);
@ -288,8 +306,24 @@ Error OS::shell_show_in_file_manager(const String &p_path, bool p_open_folder) {
return ::OS::get_singleton()->shell_show_in_file_manager(p_path, p_open_folder);
}
String OS::read_string_from_stdin() {
return ::OS::get_singleton()->get_stdin_string();
String OS::read_string_from_stdin(int64_t p_buffer_size) {
return ::OS::get_singleton()->get_stdin_string(p_buffer_size);
}
PackedByteArray OS::read_buffer_from_stdin(int64_t p_buffer_size) {
return ::OS::get_singleton()->get_stdin_buffer(p_buffer_size);
}
OS::StdHandleType OS::get_stdin_type() const {
return (OS::StdHandleType)::OS::get_singleton()->get_stdin_type();
}
OS::StdHandleType OS::get_stdout_type() const {
return (OS::StdHandleType)::OS::get_singleton()->get_stdout_type();
}
OS::StdHandleType OS::get_stderr_type() const {
return (OS::StdHandleType)::OS::get_singleton()->get_stderr_type();
}
int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr, bool p_open_console) {
@ -300,19 +334,22 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
String pipe;
int exitcode = 0;
Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr, nullptr, p_open_console);
r_output.push_back(pipe);
// Default array should never be modified, it causes the hash of the method to change.
if (!ClassDB::is_default_array_arg(r_output)) {
r_output.push_back(pipe);
}
if (err != OK) {
return -1;
}
return exitcode;
}
Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) {
Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking) {
List<String> args;
for (const String &arg : p_arguments) {
args.push_back(arg);
}
return ::OS::get_singleton()->execute_with_pipe(p_path, args);
return ::OS::get_singleton()->execute_with_pipe(p_path, args, p_blocking);
}
int OS::create_instance(const Vector<String> &p_arguments) {
@ -385,6 +422,10 @@ String OS::get_version() const {
return ::OS::get_singleton()->get_version();
}
String OS::get_version_alias() const {
return ::OS::get_singleton()->get_version_alias();
}
Vector<String> OS::get_video_adapter_driver_info() const {
return ::OS::get_singleton()->get_video_adapter_driver_info();
}
@ -450,11 +491,11 @@ Error OS::set_thread_name(const String &p_name) {
::Thread::ID OS::get_thread_caller_id() const {
return ::Thread::get_caller_id();
};
}
::Thread::ID OS::get_main_thread_id() const {
return ::Thread::get_main_id();
};
}
bool OS::has_feature(const String &p_feature) const {
const bool *value_ptr = feature_cache.getptr(p_feature);
@ -538,6 +579,11 @@ String OS::get_cache_dir() const {
return ::OS::get_singleton()->get_cache_path();
}
String OS::get_temp_dir() const {
// Exposed as `get_temp_dir()` instead of `get_temp_path()` for consistency with other exposed OS methods.
return ::OS::get_singleton()->get_temp_path();
}
bool OS::is_debug_build() const {
#ifdef DEBUG_ENABLED
return true;
@ -610,9 +656,15 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "weight", "stretch", "italic"), &OS::get_system_font_path, DEFVAL(400), DEFVAL(100), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe);
ClassDB::bind_method(D_METHOD("read_string_from_stdin", "buffer_size"), &OS::read_string_from_stdin);
ClassDB::bind_method(D_METHOD("read_buffer_from_stdin", "buffer_size"), &OS::read_buffer_from_stdin);
ClassDB::bind_method(D_METHOD("get_stdin_type"), &OS::get_stdin_type);
ClassDB::bind_method(D_METHOD("get_stdout_type"), &OS::get_stdout_type);
ClassDB::bind_method(D_METHOD("get_stderr_type"), &OS::get_stderr_type);
ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL_ARRAY, DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);
@ -630,6 +682,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_name"), &OS::get_name);
ClassDB::bind_method(D_METHOD("get_distribution_name"), &OS::get_distribution_name);
ClassDB::bind_method(D_METHOD("get_version"), &OS::get_version);
ClassDB::bind_method(D_METHOD("get_version_alias"), &OS::get_version_alias);
ClassDB::bind_method(D_METHOD("get_cmdline_args"), &OS::get_cmdline_args);
ClassDB::bind_method(D_METHOD("get_cmdline_user_args"), &OS::get_cmdline_user_args);
@ -660,6 +713,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_config_dir"), &OS::get_config_dir);
ClassDB::bind_method(D_METHOD("get_data_dir"), &OS::get_data_dir);
ClassDB::bind_method(D_METHOD("get_cache_dir"), &OS::get_cache_dir);
ClassDB::bind_method(D_METHOD("get_temp_dir"), &OS::get_temp_dir);
ClassDB::bind_method(D_METHOD("get_unique_id"), &OS::get_unique_id);
ClassDB::bind_method(D_METHOD("get_keycode_string", "code"), &OS::get_keycode_string);
@ -692,6 +746,7 @@ void OS::_bind_methods() {
BIND_ENUM_CONSTANT(RENDERING_DRIVER_VULKAN);
BIND_ENUM_CONSTANT(RENDERING_DRIVER_OPENGL3);
BIND_ENUM_CONSTANT(RENDERING_DRIVER_D3D12);
BIND_ENUM_CONSTANT(RENDERING_DRIVER_METAL);
BIND_ENUM_CONSTANT(SYSTEM_DIR_DESKTOP);
BIND_ENUM_CONSTANT(SYSTEM_DIR_DCIM);
@ -701,6 +756,12 @@ void OS::_bind_methods() {
BIND_ENUM_CONSTANT(SYSTEM_DIR_MUSIC);
BIND_ENUM_CONSTANT(SYSTEM_DIR_PICTURES);
BIND_ENUM_CONSTANT(SYSTEM_DIR_RINGTONES);
BIND_ENUM_CONSTANT(STD_HANDLE_INVALID);
BIND_ENUM_CONSTANT(STD_HANDLE_CONSOLE);
BIND_ENUM_CONSTANT(STD_HANDLE_FILE);
BIND_ENUM_CONSTANT(STD_HANDLE_PIPE);
BIND_ENUM_CONSTANT(STD_HANDLE_UNKNOWN);
}
////// Geometry2D //////
@ -901,6 +962,19 @@ Dictionary Geometry2D::make_atlas(const Vector<Size2> &p_rects) {
return ret;
}
TypedArray<Point2i> Geometry2D::bresenham_line(const Point2i &p_from, const Point2i &p_to) {
Vector<Point2i> points = ::Geometry2D::bresenham_line(p_from, p_to);
TypedArray<Point2i> result;
result.resize(points.size());
for (int i = 0; i < points.size(); i++) {
result[i] = points[i];
}
return result;
}
void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_point_in_circle", "point", "circle_position", "circle_radius"), &Geometry2D::is_point_in_circle);
ClassDB::bind_method(D_METHOD("segment_intersects_circle", "segment_from", "segment_to", "circle_position", "circle_radius"), &Geometry2D::segment_intersects_circle);
@ -935,6 +1009,8 @@ void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &Geometry2D::make_atlas);
ClassDB::bind_method(D_METHOD("bresenham_line", "from", "to"), &Geometry2D::bresenham_line);
BIND_ENUM_CONSTANT(OPERATION_UNION);
BIND_ENUM_CONSTANT(OPERATION_DIFFERENCE);
BIND_ENUM_CONSTANT(OPERATION_INTERSECTION);
@ -1209,14 +1285,15 @@ bool Semaphore::try_wait() {
return semaphore.try_wait();
}
void Semaphore::post() {
semaphore.post();
void Semaphore::post(int p_count) {
ERR_FAIL_COND(p_count <= 0);
semaphore.post(p_count);
}
void Semaphore::_bind_methods() {
ClassDB::bind_method(D_METHOD("wait"), &Semaphore::wait);
ClassDB::bind_method(D_METHOD("try_wait"), &Semaphore::try_wait);
ClassDB::bind_method(D_METHOD("post"), &Semaphore::post);
ClassDB::bind_method(D_METHOD("post", "count"), &Semaphore::post, DEFVAL(1));
}
////// Mutex //////
@ -1280,7 +1357,7 @@ void Thread::_start_func(void *ud) {
}
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
ERR_FAIL_MSG(vformat("Could not call function '%s' to start thread %d: %s.", func_name, t->get_id(), Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce)));
}
}
@ -1404,6 +1481,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const {
}
}
ClassDB::APIType ClassDB::class_get_api_type(const StringName &p_class) const {
::ClassDB::APIType api_type = ::ClassDB::get_api_type(p_class);
return (APIType)api_type;
}
bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const {
return ::ClassDB::has_signal(p_class, p_signal);
}
@ -1440,6 +1522,14 @@ TypedArray<Dictionary> ClassDB::class_get_property_list(const StringName &p_clas
return ret;
}
StringName ClassDB::class_get_property_getter(const StringName &p_class, const StringName &p_property) {
return ::ClassDB::get_property_getter(p_class, p_property);
}
StringName ClassDB::class_get_property_setter(const StringName &p_class, const StringName &p_property) {
return ::ClassDB::get_property_setter(p_class, p_property);
}
Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const {
Variant ret;
::ClassDB::get_property(p_object, p_property, ret);
@ -1492,6 +1582,23 @@ TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class,
return ret;
}
Variant ClassDB::class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
if (p_argcount < 2) {
r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
return Variant::NIL;
}
if (!p_arguments[0]->is_string() || !p_arguments[1]->is_string()) {
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
return Variant::NIL;
}
StringName class_ = *p_arguments[0];
StringName method = *p_arguments[1];
const MethodBind *bind = ::ClassDB::get_method(class_, method);
ERR_FAIL_NULL_V_MSG(bind, Variant::NIL, "Cannot find static method.");
ERR_FAIL_COND_V_MSG(!bind->is_static(), Variant::NIL, "Method is not static.");
return bind->call(nullptr, p_arguments + 2, p_argcount - 2, r_call_error);
}
PackedStringArray ClassDB::class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const {
List<String> constants;
::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance);
@ -1575,7 +1682,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List
pf == "class_has_method" || pf == "class_get_method_list" ||
pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" ||
pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" ||
pf == "is_class_enabled" || pf == "is_class_enum_bitfield");
pf == "is_class_enabled" || pf == "is_class_enum_bitfield" || pf == "class_get_api_type");
}
if (first_argument_is_class || pf == "is_parent_class") {
for (const String &E : get_class_list()) {
@ -1596,11 +1703,15 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate);
::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate);
::ClassDB::bind_method(D_METHOD("class_get_api_type", "class"), &ClassDB::class_get_api_type);
::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal);
::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal);
::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_get_property_getter", "class", "property"), &ClassDB::class_get_property_getter);
::ClassDB::bind_method(D_METHOD("class_get_property_setter", "class", "property"), &ClassDB::class_get_property_setter);
::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property);
::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property);
@ -1612,6 +1723,8 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static", &ClassDB::class_call_static, MethodInfo("class_call_static", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::class_has_integer_constant);
@ -1625,6 +1738,12 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled);
BIND_ENUM_CONSTANT(API_CORE);
BIND_ENUM_CONSTANT(API_EDITOR);
BIND_ENUM_CONSTANT(API_EXTENSION);
BIND_ENUM_CONSTANT(API_EDITOR_EXTENSION);
BIND_ENUM_CONSTANT(API_NONE);
}
} // namespace special
@ -1737,8 +1856,8 @@ Object *Engine::get_singleton_object(const StringName &p_name) const {
}
void Engine::register_singleton(const StringName &p_name, Object *p_object) {
ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name));
ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name);
ERR_FAIL_COND_MSG(has_singleton(p_name), vformat("Singleton already registered: '%s'.", String(p_name)));
ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), vformat("Singleton name is not a valid identifier: '%s'.", p_name));
::Engine::Singleton s;
s.class_name = p_name;
s.name = p_name;
@ -1748,8 +1867,8 @@ void Engine::register_singleton(const StringName &p_name, Object *p_object) {
}
void Engine::unregister_singleton(const StringName &p_name) {
ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregistered singleton: " + String(p_name));
ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name));
ERR_FAIL_COND_MSG(!has_singleton(p_name), vformat("Attempt to remove unregistered singleton: '%s'.", String(p_name)));
ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), vformat("Attempt to remove non-user created singleton: '%s'.", String(p_name)));
::Engine::get_singleton()->remove_singleton(p_name);
}
@ -1787,10 +1906,22 @@ bool Engine::is_editor_hint() const {
return ::Engine::get_singleton()->is_editor_hint();
}
bool Engine::is_embedded_in_editor() const {
return ::Engine::get_singleton()->is_embedded_in_editor();
}
String Engine::get_write_movie_path() const {
return ::Engine::get_singleton()->get_write_movie_path();
}
void Engine::set_print_to_stdout(bool p_enabled) {
::Engine::get_singleton()->set_print_to_stdout(p_enabled);
}
bool Engine::is_printing_to_stdout() const {
return ::Engine::get_singleton()->is_printing_to_stdout();
}
void Engine::set_print_error_messages(bool p_enabled) {
::Engine::get_singleton()->set_print_error_messages(p_enabled);
}
@ -1856,13 +1987,18 @@ void Engine::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_script_language", "index"), &Engine::get_script_language);
ClassDB::bind_method(D_METHOD("is_editor_hint"), &Engine::is_editor_hint);
ClassDB::bind_method(D_METHOD("is_embedded_in_editor"), &Engine::is_embedded_in_editor);
ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path);
ClassDB::bind_method(D_METHOD("set_print_to_stdout", "enabled"), &Engine::set_print_to_stdout);
ClassDB::bind_method(D_METHOD("is_printing_to_stdout"), &Engine::is_printing_to_stdout);
ClassDB::bind_method(D_METHOD("set_print_error_messages", "enabled"), &Engine::set_print_error_messages);
ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &Engine::is_printing_error_messages);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_to_stdout"), "set_print_to_stdout", "is_printing_to_stdout");
ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_physics_steps_per_frame"), "set_max_physics_steps_per_frame", "get_max_physics_steps_per_frame");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_fps"), "set_max_fps", "get_max_fps");
@ -1881,14 +2017,14 @@ bool EngineDebugger::is_active() {
void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
ERR_FAIL_COND(p_profiler.is_null());
ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), vformat("Profiler name already in use: '%s'.", p_name));
Error err = p_profiler->bind(p_name);
ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
ERR_FAIL_COND_MSG(err != OK, vformat("Profiler failed to register with error: %d.", err));
profilers.insert(p_name, p_profiler);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
profilers[p_name]->unbind();
profilers.erase(p_name);
}
@ -1912,7 +2048,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) {
ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name);
ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_callable);
Callable &c = captures[p_name];
::EngineDebugger::Capture capture(&c, &EngineDebugger::call_capture);
@ -1920,7 +2056,7 @@ void EngineDebugger::register_message_capture(const StringName &p_name, const Ca
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
::EngineDebugger::unregister_message_capture(p_name);
captures.erase(p_name);
}
@ -1954,8 +2090,8 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra
Variant retval;
Callable::CallError err;
capture.callp(args, 2, retval, err);
ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(capture, args, 2, err));
ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, "Error calling 'capture' to callable: " + String(capture) + ". Return type is not bool.");
ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, vformat("Error calling 'capture' to callable: %s.", Variant::get_callable_error_text(capture, args, 2, err)));
ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, vformat("Error calling 'capture' to callable: '%s'. Return type is not bool.", String(capture)));
r_captured = retval;
return OK;
}

View file

@ -32,11 +32,8 @@
#define CORE_BIND_H
#include "core/debugger/engine_profiler.h"
#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/os/semaphore.h"
#include "core/os/thread.h"
#include "core/templates/safe_refcount.h"
@ -73,7 +70,7 @@ public:
static ResourceLoader *get_singleton() { return singleton; }
Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, CacheMode p_cache_mode = CACHE_MODE_REUSE);
ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array());
ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = ClassDB::default_array_arg);
Ref<Resource> load_threaded_get(const String &p_path);
Ref<Resource> load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE);
@ -83,9 +80,12 @@ public:
void set_abort_on_missing_resources(bool p_abort);
PackedStringArray get_dependencies(const String &p_path);
bool has_cached(const String &p_path);
Ref<Resource> get_cached_ref(const String &p_path);
bool exists(const String &p_path, const String &p_type_hint = "");
ResourceUID::ID get_resource_uid(const String &p_path);
Vector<String> list_directory(const String &p_directory);
ResourceLoader() { singleton = this; }
};
@ -115,6 +115,8 @@ public:
void add_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver, bool p_at_front);
void remove_resource_format_saver(Ref<ResourceFormatSaver> p_format_saver);
ResourceUID::ID get_resource_id_for_path(const String &p_path, bool p_generate = false);
ResourceSaver() { singleton = this; }
};
@ -127,16 +129,32 @@ protected:
static void _bind_methods();
static OS *singleton;
#ifndef DISABLE_DEPRECATED
Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments);
String _read_string_from_stdin_bind_compat_91201();
static void _bind_compatibility_methods();
#endif
public:
enum RenderingDriver {
RENDERING_DRIVER_VULKAN,
RENDERING_DRIVER_OPENGL3,
RENDERING_DRIVER_D3D12,
RENDERING_DRIVER_METAL,
};
PackedByteArray get_entropy(int p_bytes);
String get_system_ca_certificates();
enum StdHandleType {
STD_HANDLE_INVALID,
STD_HANDLE_CONSOLE,
STD_HANDLE_FILE,
STD_HANDLE_PIPE,
STD_HANDLE_UNKNOWN,
};
virtual PackedStringArray get_connected_midi_inputs();
virtual void open_midi_inputs();
virtual void close_midi_inputs();
@ -157,9 +175,15 @@ public:
String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
String get_executable_path() const;
String read_string_from_stdin();
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments);
String read_string_from_stdin(int64_t p_buffer_size = 1024);
PackedByteArray read_buffer_from_stdin(int64_t p_buffer_size = 1024);
StdHandleType get_stdin_type() const;
StdHandleType get_stdout_type() const;
StdHandleType get_stderr_type() const;
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = ClassDB::default_array_arg, bool p_read_stderr = false, bool p_open_console = false);
Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid);
@ -182,6 +206,7 @@ public:
String get_name() const;
String get_distribution_name() const;
String get_version() const;
String get_version_alias() const;
Vector<String> get_cmdline_args();
Vector<String> get_cmdline_user_args();
@ -236,6 +261,7 @@ public:
String get_config_dir() const;
String get_data_dir() const;
String get_cache_dir() const;
String get_temp_dir() const;
Error set_thread_name(const String &p_name);
::Thread::ID get_thread_caller_id() const;
@ -315,6 +341,8 @@ public:
Dictionary make_atlas(const Vector<Size2> &p_rects);
TypedArray<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to);
Geometry2D() { singleton = this; }
};
@ -389,12 +417,17 @@ class Semaphore : public RefCounted {
GDCLASS(Semaphore, RefCounted);
::Semaphore semaphore;
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _post_bind_compat_93605();
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
void wait();
bool try_wait();
void post();
void post(int p_count = 1);
};
class Thread : public RefCounted {
@ -434,6 +467,14 @@ protected:
static void _bind_methods();
public:
enum APIType {
API_CORE,
API_EDITOR,
API_EXTENSION,
API_EDITOR_EXTENSION,
API_NONE,
};
PackedStringArray get_class_list() const;
PackedStringArray get_inheriters_from_class(const StringName &p_class) const;
StringName get_parent_class(const StringName &p_class) const;
@ -442,11 +483,14 @@ public:
bool can_instantiate(const StringName &p_class) const;
Variant instantiate(const StringName &p_class) const;
APIType class_get_api_type(const StringName &p_class) const;
bool class_has_signal(const StringName &p_class, const StringName &p_signal) const;
Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const;
TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const;
StringName class_get_property_getter(const StringName &p_class, const StringName &p_property);
StringName class_get_property_setter(const StringName &p_class, const StringName &p_property);
Variant class_get_property(Object *p_object, const StringName &p_property) const;
Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const;
@ -457,6 +501,7 @@ public:
int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
Variant class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
@ -539,9 +584,14 @@ public:
void set_editor_hint(bool p_enabled);
bool is_editor_hint() const;
bool is_embedded_in_editor() const;
// `set_write_movie_path()` is not exposed to the scripting API as changing it at run-time has no effect.
String get_write_movie_path() const;
void set_print_to_stdout(bool p_enabled);
bool is_printing_to_stdout() const;
void set_print_error_messages(bool p_enabled);
bool is_printing_error_messages() const;
@ -611,6 +661,7 @@ VARIANT_BITFIELD_CAST(core_bind::ResourceSaver::SaverFlags);
VARIANT_ENUM_CAST(core_bind::OS::RenderingDriver);
VARIANT_ENUM_CAST(core_bind::OS::SystemDir);
VARIANT_ENUM_CAST(core_bind::OS::StdHandleType);
VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyBooleanOperation);
VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyJoinType);
@ -618,4 +669,6 @@ VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyEndType);
VARIANT_ENUM_CAST(core_bind::Thread::Priority);
VARIANT_ENUM_CAST(core_bind::special::ClassDB::APIType);
#endif // CORE_BIND_H

View file

@ -200,8 +200,8 @@ def make_license_header(target, source, env):
tag, content = reader.next_tag()
if tag in ("Files", "Copyright", "License"):
part[tag] = content[:]
elif tag == "Comment":
# attach part to named project
elif tag == "Comment" and part:
# attach non-empty part to named project
projects[content[0]] = projects.get(content[0], []) + [part]
if not tag or not reader.current:

View file

@ -33,7 +33,6 @@
#include "core/input/input_event.h"
#include "core/object/class_db.h"
#include "core/os/keyboard.h"
#include "core/templates/hash_set.h"
#include "core/variant/variant.h"
struct _CoreConstant {
@ -671,11 +670,14 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_HIDE_QUATERNION_EDIT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PASSWORD);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_TOOL_BUTTON);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ONESHOT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NONE);

View file

@ -32,7 +32,7 @@
#define CORE_CONSTANTS_H
#include "core/string/string_name.h"
#include "core/templates/hash_set.h"
#include "core/templates/hash_map.h"
class CoreConstants {
public:

View file

@ -34,61 +34,56 @@
#include "core/string/string_name.h"
class CoreStringNames {
friend void register_core_types();
friend void unregister_core_types();
inline static CoreStringNames *singleton = nullptr;
public:
static void create() { singleton = memnew(CoreStringNames); }
static void free() {
memdelete(singleton);
singleton = nullptr;
}
CoreStringNames();
public:
_FORCE_INLINE_ static CoreStringNames *get_singleton() { return singleton; }
static CoreStringNames *singleton;
const StringName free_ = StaticCString::create("free"); // free would conflict with C++ keyword.
const StringName changed = StaticCString::create("changed");
const StringName script = StaticCString::create("script");
const StringName script_changed = StaticCString::create("script_changed");
const StringName _iter_init = StaticCString::create("_iter_init");
const StringName _iter_next = StaticCString::create("_iter_next");
const StringName _iter_get = StaticCString::create("_iter_get");
const StringName get_rid = StaticCString::create("get_rid");
const StringName _to_string = StaticCString::create("_to_string");
const StringName _custom_features = StaticCString::create("_custom_features");
StringName free_; // "free", conflict with C++ keyword.
StringName changed;
StringName script;
StringName script_changed;
StringName _iter_init;
StringName _iter_next;
StringName _iter_get;
StringName get_rid;
StringName _to_string;
StringName _custom_features;
const StringName x = StaticCString::create("x");
const StringName y = StaticCString::create("y");
const StringName z = StaticCString::create("z");
const StringName w = StaticCString::create("w");
const StringName r = StaticCString::create("r");
const StringName g = StaticCString::create("g");
const StringName b = StaticCString::create("b");
const StringName a = StaticCString::create("a");
const StringName position = StaticCString::create("position");
const StringName size = StaticCString::create("size");
const StringName end = StaticCString::create("end");
const StringName basis = StaticCString::create("basis");
const StringName origin = StaticCString::create("origin");
const StringName normal = StaticCString::create("normal");
const StringName d = StaticCString::create("d");
const StringName h = StaticCString::create("h");
const StringName s = StaticCString::create("s");
const StringName v = StaticCString::create("v");
const StringName r8 = StaticCString::create("r8");
const StringName g8 = StaticCString::create("g8");
const StringName b8 = StaticCString::create("b8");
const StringName a8 = StaticCString::create("a8");
StringName x;
StringName y;
StringName z;
StringName w;
StringName r;
StringName g;
StringName b;
StringName a;
StringName position;
StringName size;
StringName end;
StringName basis;
StringName origin;
StringName normal;
StringName d;
StringName h;
StringName s;
StringName v;
StringName r8;
StringName g8;
StringName b8;
StringName a8;
StringName call;
StringName call_deferred;
StringName bind;
StringName notification;
StringName property_list_changed;
const StringName call = StaticCString::create("call");
const StringName call_deferred = StaticCString::create("call_deferred");
const StringName bind = StaticCString::create("bind");
const StringName notification = StaticCString::create("notification");
const StringName property_list_changed = StaticCString::create("property_list_changed");
};
#define CoreStringName(m_name) CoreStringNames::get_singleton()->m_name

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -38,7 +38,7 @@ class AESContext : public RefCounted {
GDCLASS(AESContext, RefCounted);
public:
enum Mode {
enum Mode : int32_t {
MODE_ECB_ENCRYPT,
MODE_ECB_DECRYPT,
MODE_CBC_ENCRYPT,

View file

@ -30,16 +30,12 @@
#include "crypto.h"
#include "core/config/engine.h"
#include "core/io/certs_compressed.gen.h"
#include "core/io/compression.h"
/// Resources
CryptoKey *(*CryptoKey::_create)() = nullptr;
CryptoKey *CryptoKey::create() {
CryptoKey *(*CryptoKey::_create)(bool p_notify_postinitialize) = nullptr;
CryptoKey *CryptoKey::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
return nullptr;
}
@ -52,10 +48,10 @@ void CryptoKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_from_string", "string_key", "public_only"), &CryptoKey::load_from_string, DEFVAL(false));
}
X509Certificate *(*X509Certificate::_create)() = nullptr;
X509Certificate *X509Certificate::create() {
X509Certificate *(*X509Certificate::_create)(bool p_notify_postinitialize) = nullptr;
X509Certificate *X509Certificate::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
return nullptr;
}
@ -116,10 +112,10 @@ void HMACContext::_bind_methods() {
ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish);
}
HMACContext *(*HMACContext::_create)() = nullptr;
HMACContext *HMACContext::create() {
HMACContext *(*HMACContext::_create)(bool p_notify_postinitialize) = nullptr;
HMACContext *HMACContext::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled.");
}
@ -127,10 +123,10 @@ HMACContext *HMACContext::create() {
/// Crypto
void (*Crypto::_load_default_certificates)(const String &p_path) = nullptr;
Crypto *(*Crypto::_create)() = nullptr;
Crypto *Crypto::create() {
Crypto *(*Crypto::_create)(bool p_notify_postinitialize) = nullptr;
Crypto *Crypto::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
ERR_FAIL_V_MSG(nullptr, "Crypto is not available when the mbedtls module is disabled.");
}
@ -240,7 +236,7 @@ Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const Str
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Crypto resource to file '" + p_path + "'.");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save Crypto resource to file '%s'.", p_path));
return OK;
}

View file

@ -42,10 +42,10 @@ class CryptoKey : public Resource {
protected:
static void _bind_methods();
static CryptoKey *(*_create)();
static CryptoKey *(*_create)(bool p_notify_postinitialize);
public:
static CryptoKey *create();
static CryptoKey *create(bool p_notify_postinitialize = true);
virtual Error load(const String &p_path, bool p_public_only = false) = 0;
virtual Error save(const String &p_path, bool p_public_only = false) = 0;
virtual String save_to_string(bool p_public_only = false) = 0;
@ -58,10 +58,10 @@ class X509Certificate : public Resource {
protected:
static void _bind_methods();
static X509Certificate *(*_create)();
static X509Certificate *(*_create)(bool p_notify_postinitialize);
public:
static X509Certificate *create();
static X509Certificate *create(bool p_notify_postinitialize = true);
virtual Error load(const String &p_path) = 0;
virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0;
virtual Error save(const String &p_path) = 0;
@ -106,10 +106,10 @@ class HMACContext : public RefCounted {
protected:
static void _bind_methods();
static HMACContext *(*_create)();
static HMACContext *(*_create)(bool p_notify_postinitialize);
public:
static HMACContext *create();
static HMACContext *create(bool p_notify_postinitialize = true);
virtual Error start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) = 0;
virtual Error update(const PackedByteArray &p_data) = 0;
@ -124,11 +124,11 @@ class Crypto : public RefCounted {
protected:
static void _bind_methods();
static Crypto *(*_create)();
static Crypto *(*_create)(bool p_notify_postinitialize);
static void (*_load_default_certificates)(const String &p_path);
public:
static Crypto *create();
static Crypto *create(bool p_notify_postinitialize = true);
static void load_default_certificates(const String &p_path);
virtual PackedByteArray generate_random_bytes(int p_bytes) = 0;
@ -155,6 +155,10 @@ public:
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
// Treat certificates as text files, do not generate a `*.{crt,key,pub}.uid` file.
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override { return ResourceUID::INVALID_ID; }
virtual bool has_custom_uid_support() const override { return true; }
};
class ResourceFormatSaverCrypto : public ResourceFormatSaver {

View file

@ -70,7 +70,7 @@ int CryptoCore::RandomGenerator::_entropy_poll(void *p_data, unsigned char *r_bu
Error CryptoCore::RandomGenerator::init() {
int ret = mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context *)ctx, mbedtls_entropy_func, (mbedtls_entropy_context *)entropy, nullptr, 0);
if (ret) {
ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
}
return OK;
}
@ -78,7 +78,7 @@ Error CryptoCore::RandomGenerator::init() {
Error CryptoCore::RandomGenerator::get_random_bytes(uint8_t *r_buffer, size_t p_bytes) {
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
int ret = mbedtls_ctr_drbg_random((mbedtls_ctr_drbg_context *)ctx, r_buffer, p_bytes);
ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
return OK;
}

View file

@ -37,7 +37,7 @@ class HashingContext : public RefCounted {
GDCLASS(HashingContext, RefCounted);
public:
enum HashType {
enum HashType : int32_t {
HASH_MD5,
HASH_SHA1,
HASH_SHA256

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -147,3 +147,37 @@ bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
CHECK_END(p_arr, idx, "OutputError");
return true;
}
Array DebuggerMarshalls::serialize_key_shortcut(const Ref<Shortcut> &p_shortcut) {
ERR_FAIL_COND_V(p_shortcut.is_null(), Array());
Array keys;
for (const Ref<InputEvent> ev : p_shortcut->get_events()) {
const Ref<InputEventKey> kev = ev;
ERR_CONTINUE(kev.is_null());
if (kev->get_physical_keycode() != Key::NONE) {
keys.push_back(true);
keys.push_back(kev->get_physical_keycode_with_modifiers());
} else {
keys.push_back(false);
keys.push_back(kev->get_keycode_with_modifiers());
}
}
return keys;
}
Ref<Shortcut> DebuggerMarshalls::deserialize_key_shortcut(const Array &p_keys) {
Array key_events;
ERR_FAIL_COND_V(p_keys.size() % 2 != 0, Ref<Shortcut>());
for (int i = 0; i < p_keys.size(); i += 2) {
ERR_CONTINUE(p_keys[i].get_type() != Variant::BOOL);
ERR_CONTINUE(p_keys[i + 1].get_type() != Variant::INT);
key_events.push_back(InputEventKey::create_reference((Key)p_keys[i + 1].operator int(), p_keys[i].operator bool()));
}
if (key_events.is_empty()) {
return Ref<Shortcut>();
}
Ref<Shortcut> shortcut;
shortcut.instantiate();
shortcut->set_events(key_events);
return shortcut;
}

View file

@ -31,6 +31,7 @@
#ifndef DEBUGGER_MARSHALLS_H
#define DEBUGGER_MARSHALLS_H
#include "core/input/shortcut.h"
#include "core/object/script_language.h"
struct DebuggerMarshalls {
@ -68,6 +69,9 @@ struct DebuggerMarshalls {
Array serialize();
bool deserialize(const Array &p_arr);
};
static Array serialize_key_shortcut(const Ref<Shortcut> &p_shortcut);
static Ref<Shortcut> deserialize_key_shortcut(const Array &p_keys);
};
#endif // DEBUGGER_MARSHALLS_H

View file

@ -46,12 +46,12 @@ HashMap<String, EngineDebugger::CreatePeerFunc> EngineDebugger::protocols;
void (*EngineDebugger::allow_focus_steal_fn)();
void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
ERR_FAIL_COND_MSG(profilers.has(p_name), vformat("Profiler already registered: '%s'.", p_name));
profilers.insert(p_name, p_func);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.active && p.toggle) {
p.toggle(p.data, false, Array());
@ -61,22 +61,22 @@ void EngineDebugger::unregister_profiler(const StringName &p_name) {
}
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
ERR_FAIL_COND_MSG(captures.has(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_func);
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
captures.erase(p_name);
}
void EngineDebugger::register_uri_handler(const String &p_protocol, CreatePeerFunc p_func) {
ERR_FAIL_COND_MSG(protocols.has(p_protocol), "Protocol handler already registered: " + p_protocol);
ERR_FAIL_COND_MSG(protocols.has(p_protocol), vformat("Protocol handler already registered: '%s'.", p_protocol));
protocols.insert(p_protocol, p_func);
}
void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't change profiler state, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.toggle) {
p.toggle(p.data, p_enabled, p_opts);
@ -85,7 +85,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't add frame data, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.add) {
p.add(p.data, p_data);
@ -106,7 +106,7 @@ bool EngineDebugger::has_capture(const StringName &p_name) {
Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = false;
ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, vformat("Capture not registered: '%s'.", p_name));
const Capture &cap = captures[p_name];
return cap.capture(cap.data, p_msg, p_args, r_captured);
}
@ -163,8 +163,8 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
for (int i = 0; i < p_breakpoints.size(); i++) {
const String &bp = p_breakpoints[i];
int sp = bp.rfind(":");
ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
int sp = bp.rfind_char(':');
ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp));
singleton_script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}

View file

@ -36,7 +36,6 @@
#include "core/templates/hash_map.h"
#include "core/templates/vector.h"
#include "core/variant/array.h"
#include "core/variant/variant.h"
class RemoteDebuggerPeer;
class ScriptDebugger;
@ -106,7 +105,7 @@ public:
_FORCE_INLINE_ static EngineDebugger *get_singleton() { return singleton; }
_FORCE_INLINE_ static bool is_active() { return singleton != nullptr && script_debugger != nullptr; }
_FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; };
_FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; }
static void initialize(const String &p_uri, bool p_skip_breakpoints, const Vector<String> &p_breakpoints, void (*p_allow_focus_steal_fn)());
static void deinitialize();

View file

@ -171,7 +171,7 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} else {
String key_value = line.get_slicec(' ', 1);
int value_pos = key_value.find("=");
int value_pos = key_value.find_char('=');
if (value_pos < 0) {
print_line("Error: Invalid set format. Use: set key=value");
@ -208,10 +208,10 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
print_variables(members, values, variable_prefix);
} else if (line.begins_with("p") || line.begins_with("print")) {
if (line.get_slice_count(" ") <= 1) {
print_line("Usage: print <expre>");
if (line.find_char(' ') < 0) {
print_line("Usage: print <expression>");
} else {
String expr = line.get_slicec(' ', 2);
String expr = line.split(" ", true, 1)[1];
String res = script_lang->debug_parse_stack_level_expression(current_frame, expr);
print_line(res);
}
@ -344,7 +344,7 @@ Pair<String, int> LocalDebugger::to_breakpoint(const String &p_line) {
String breakpoint_part = p_line.get_slicec(' ', 1);
Pair<String, int> breakpoint;
int last_colon = breakpoint_part.rfind(":");
int last_colon = breakpoint_part.rfind_char(':');
if (last_colon < 0) {
print_line("Error: Invalid breakpoint format. Expected [source:line]");
return breakpoint;

View file

@ -37,6 +37,7 @@
#include "core/debugger/script_debugger.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
#include "core/math/expression.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "servers/display_server.h"
@ -78,7 +79,7 @@ public:
for (int i = 0; i < custom_monitor_names.size(); i++) {
Variant monitor_value = performance->call("get_custom_monitor", custom_monitor_names[i]);
if (!monitor_value.is_num()) {
ERR_PRINT("Value of custom monitor '" + String(custom_monitor_names[i]) + "' is not a number");
ERR_PRINT(vformat("Value of custom monitor '%s' is not a number.", String(custom_monitor_names[i])));
arr[i + max] = Variant();
} else {
arr[i + max] = monitor_value;
@ -337,7 +338,7 @@ void RemoteDebugger::_send_stack_vars(List<String> &p_names, List<Variant> &p_va
}
Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, bool &r_captured) {
const int idx = p_msg.find(":");
const int idx = p_msg.find_char(':');
r_captured = false;
if (idx < 0) { // No prefix, unknown message.
return OK;
@ -529,11 +530,46 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} else if (command == "set_skip_breakpoints") {
ERR_FAIL_COND(data.is_empty());
script_debugger->set_skip_breakpoints(data[0]);
} else if (command == "evaluate") {
String expression_str = data[0];
int frame = data[1];
ScriptInstance *breaked_instance = script_debugger->get_break_language()->debug_get_stack_level_instance(frame);
if (!breaked_instance) {
break;
}
List<String> locals;
List<Variant> local_vals;
script_debugger->get_break_language()->debug_get_stack_level_locals(frame, &locals, &local_vals);
ERR_FAIL_COND(locals.size() != local_vals.size());
PackedStringArray locals_vector;
for (const String &S : locals) {
locals_vector.append(S);
}
Array local_vals_array;
for (const Variant &V : local_vals) {
local_vals_array.append(V);
}
Expression expression;
expression.parse(expression_str, locals_vector);
const Variant return_val = expression.execute(local_vals_array, breaked_instance->get_owner());
DebuggerMarshalls::ScriptStackVariable stvar;
stvar.name = expression_str;
stvar.value = return_val;
stvar.type = 3;
send_message("evaluation_return", stvar.serialize());
} else {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
if (!captured) {
WARN_PRINT("Unknown message received from debugger: " + command);
WARN_PRINT(vformat("Unknown message received from debugger: %s.", command));
}
}
} else {
@ -574,7 +610,7 @@ void RemoteDebugger::poll_events(bool p_is_idle) {
ERR_CONTINUE(arr[1].get_type() != Variant::ARRAY);
const String cmd = arr[0];
const int idx = cmd.find(":");
const int idx = cmd.find_char(':');
bool parsed = false;
if (idx < 0) { // Not prefix, use scripts capture.
capture_parse("core", cmd, arr[1], parsed);

View file

@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() {
Error err = decode_variant(var, buf, in_pos, &read);
ERR_CONTINUE(read != in_pos || err != OK);
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
mutex.lock();
MutexLock lock(mutex);
in_queue.push_back(var);
mutex.unlock();
}
}
}
@ -174,12 +173,12 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
} else {
const int ms = waits[i];
OS::get_singleton()->delay_usec(ms * 1000);
print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
print_verbose("Remote Debugger: Connection failed with status: '" + String::num_int64(tcp_client->get_status()) + "', retrying in " + String::num_int64(ms) + " msec.");
}
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num_int64(tcp_client->get_status())));
return FAILED;
}
connected = true;
@ -224,8 +223,8 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) {
String debug_host = p_uri.replace("tcp://", "");
uint16_t debug_port = 6007;
if (debug_host.contains(":")) {
int sep_pos = debug_host.rfind(":");
if (debug_host.contains_char(':')) {
int sep_pos = debug_host.rfind_char(':');
debug_port = debug_host.substr(sep_pos + 1).to_int();
debug_host = debug_host.substr(0, sep_pos);
}

View file

@ -34,7 +34,6 @@
#include "core/object/script_language.h"
#include "core/string/string_name.h"
#include "core/templates/hash_set.h"
#include "core/templates/rb_map.h"
#include "core/templates/vector.h"
class ScriptDebugger {

View file

@ -33,6 +33,8 @@
String DocData::get_default_value_string(const Variant &p_value) {
if (p_value.get_type() == Variant::ARRAY) {
return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
} else if (p_value.get_type() == Variant::DICTIONARY) {
return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
} else {
return p_value.get_construct_string().replace("\n", " ");
}
@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
p_method.return_type = p_retinfo.class_name;
} else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_method.return_type = p_retinfo.hint_string + "[]";
} else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]";
} else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_method.return_type = p_retinfo.hint_string;
} else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
p_argument.type = p_arginfo.class_name;
} else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_argument.type = p_arginfo.hint_string + "[]";
} else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]";
} else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_argument.type = p_arginfo.hint_string;
} else if (p_arginfo.type == Variant::NIL) {
@ -99,28 +105,6 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
}
}
void DocData::property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo) {
p_property.name = p_memberinfo.propinfo.name;
p_property.description = p_memberinfo.doc_string;
if (p_memberinfo.propinfo.type == Variant::OBJECT) {
p_property.type = p_memberinfo.propinfo.class_name;
} else if (p_memberinfo.propinfo.type == Variant::NIL && p_memberinfo.propinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
p_property.type = "Variant";
} else {
p_property.type = Variant::get_type_name(p_memberinfo.propinfo.type);
}
p_property.setter = p_memberinfo.setter;
p_property.getter = p_memberinfo.getter;
if (p_memberinfo.has_default_value && p_memberinfo.default_value.get_type() != Variant::OBJECT) {
p_property.default_value = get_default_value_string(p_memberinfo.default_value);
}
p_property.overridden = false;
}
void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc) {
p_method.name = p_methodinfo.name;
p_method.description = p_desc;
@ -164,14 +148,3 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met
p_method.arguments.push_back(argument);
}
}
void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) {
p_const.name = p_name;
p_const.value = p_value;
p_const.is_value_valid = (p_value.get_type() != Variant::OBJECT);
p_const.description = p_desc;
}
void DocData::signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc) {
return method_doc_from_methodinfo(p_signal, p_methodinfo, p_desc);
}

View file

@ -34,16 +34,6 @@
#include "core/io/xml_parser.h"
#include "core/variant/variant.h"
struct ScriptMemberInfo {
PropertyInfo propinfo;
String doc_string;
StringName setter;
StringName getter;
bool has_default_value = false;
Variant default_value;
};
class DocData {
public:
struct ArgumentDoc {
@ -276,6 +266,7 @@ public:
String name;
String value;
bool is_value_valid = false;
String type;
String enumeration;
bool is_bitfield = false;
String description;
@ -302,6 +293,10 @@ public:
doc.is_value_valid = p_dict["is_value_valid"];
}
if (p_dict.has("type")) {
doc.type = p_dict["type"];
}
if (p_dict.has("enumeration")) {
doc.enumeration = p_dict["enumeration"];
if (p_dict.has("is_bitfield")) {
@ -352,6 +347,8 @@ public:
dict["is_value_valid"] = p_doc.is_value_valid;
dict["type"] = p_doc.type;
if (!p_doc.enumeration.is_empty()) {
dict["enumeration"] = p_doc.enumeration;
dict["is_bitfield"] = p_doc.is_bitfield;
@ -522,6 +519,10 @@ public:
String type;
String data_type;
String description;
bool is_deprecated = false;
String deprecated_message;
bool is_experimental = false;
String experimental_message;
String default_value;
String keywords;
bool operator<(const ThemeItemDoc &p_theme_item) const {
@ -550,6 +551,16 @@ public:
doc.description = p_dict["description"];
}
if (p_dict.has("deprecated")) {
doc.is_deprecated = true;
doc.deprecated_message = p_dict["deprecated"];
}
if (p_dict.has("experimental")) {
doc.is_experimental = true;
doc.experimental_message = p_dict["experimental"];
}
if (p_dict.has("default_value")) {
doc.default_value = p_dict["default_value"];
}
@ -579,6 +590,14 @@ public:
dict["description"] = p_doc.description;
}
if (p_doc.is_deprecated) {
dict["deprecated"] = p_doc.deprecated_message;
}
if (p_doc.is_experimental) {
dict["experimental"] = p_doc.experimental_message;
}
if (!p_doc.default_value.is_empty()) {
dict["default_value"] = p_doc.default_value;
}
@ -959,10 +978,7 @@ public:
static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo);
static void argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo);
static void property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo);
static void method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc);
static void constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc);
static void signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc);
};
#endif // DOC_DATA_H

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -41,6 +41,7 @@
* - Are added to the Error enum in core/error/error_list.h
* - Have a description added to error_names in core/error/error_list.cpp
* - Are bound with BIND_CORE_ENUM_CONSTANT() in core/core_constants.cpp
* - Have a matching Android version in platform/android/java/lib/src/org/godotengine/godot/error/Error.kt
*/
enum Error {

View file

@ -34,6 +34,12 @@
#include "core/os/os.h"
#include "core/string/ustring.h"
// Optional physics interpolation warnings try to include the path to the relevant node.
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
#include "core/config/project_settings.h"
#include "scene/main/node.h"
#endif
static ErrorHandlerList *error_handler_list = nullptr;
void add_error_handler(ErrorHandlerList *p_handler) {
@ -101,6 +107,28 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
_global_unlock();
}
// For printing errors when we may crash at any point, so we must flush ASAP a lot of lines
// but we don't want to make it noisy by printing lots of file & line info (because it's already
// been printing by a preceding _err_print_error).
void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type) {
if (OS::get_singleton()) {
OS::get_singleton()->printerr("ERROR: %s\n", p_error.utf8().get_data());
} else {
// Fallback if errors happen before OS init or after it's destroyed.
const char *err_details = p_error.utf8().get_data();
fprintf(stderr, "ERROR: %s\n", err_details);
}
_global_lock();
ErrorHandlerList *l = error_handler_list;
while (l) {
l->errfunc(l->userdata, "", "", 0, p_error.utf8().get_data(), "", false, p_type);
l = l->next;
}
_global_unlock();
}
// Errors with message. (All combinations of p_error and p_message as String or char*.)
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
_err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_editor_notify, p_type);
@ -128,3 +156,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
void _err_flush_stdout() {
fflush(stdout);
}
// Prevent error spam by limiting the warnings to a certain frequency.
void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) {
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
const uint32_t warn_max = 2048;
const uint32_t warn_timeout_seconds = 15;
static uint32_t warn_count = warn_max;
static uint32_t warn_timeout = warn_timeout_seconds;
uint32_t time_now = UINT32_MAX;
if (warn_count) {
warn_count--;
}
if (!warn_count) {
time_now = OS::get_singleton()->get_ticks_msec() / 1000;
}
if ((warn_count == 0) && (time_now >= warn_timeout)) {
warn_count = warn_max;
warn_timeout = time_now + warn_timeout_seconds;
if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) {
// UINT64_MAX means unused.
if (p_id.operator uint64_t() == UINT64_MAX) {
_err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING);
} else {
String node_name;
if (p_id.is_valid()) {
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
if (node && node->is_inside_tree()) {
node_name = "\"" + String(node->get_path()) + "\"";
} else {
node_name = "\"unknown\"";
}
}
_err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING);
}
}
}
#endif
}

View file

@ -31,6 +31,7 @@
#ifndef ERROR_MACROS_H
#define ERROR_MACROS_H
#include "core/object/object_id.h"
#include "core/typedefs.h"
#include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes.
@ -67,10 +68,13 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false);
void _err_flush_stdout();
void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string);
#ifdef __GNUC__
//#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying
#define FUNCTION_STR __FUNCTION__
@ -832,4 +836,14 @@ void _err_flush_stdout();
#define DEV_CHECK_ONCE(m_cond)
#endif
/**
* Physics Interpolation warnings.
* These are spam protection warnings.
*/
#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \
_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string)
#define PHYSICS_INTERPOLATION_WARNING(m_string) \
_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, ObjectID(UINT64_MAX), m_string)
#endif // ERROR_MACROS_H

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -32,7 +32,7 @@
#include "core/config/engine.h"
#include "core/core_constants.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_special_compat_hashes.h"
#include "core/io/file_access.h"
#include "core/io/json.h"
#include "core/templates/pair.h"
@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) {
return String("typedarray::") + p_info.hint_string;
}
if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) {
return String("typeddictionary::") + p_info.hint_string;
}
if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) {
return String("enum::") + String(p_info.class_name);
}
@ -85,7 +88,7 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
}
static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) {
static const char *argmeta[11] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double" };
static const char *argmeta[13] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32" };
return argmeta[metadata];
}
@ -1014,9 +1017,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
d2["name"] = String(method_name);
d2["is_const"] = (F.flags & METHOD_FLAG_CONST) ? true : false;
d2["is_static"] = (F.flags & METHOD_FLAG_STATIC) ? true : false;
d2["is_required"] = (F.flags & METHOD_FLAG_VIRTUAL_REQUIRED) ? true : false;
d2["is_vararg"] = false;
d2["is_virtual"] = true;
// virtual functions have no hash since no MethodBind is involved
d2["hash"] = mi.get_compatibility_hash();
Vector<uint32_t> compat_hashes = ClassDB::get_virtual_method_compatibility_hashes(class_name, method_name);
Array compatibility;
if (compat_hashes.size()) {
for (int i = 0; i < compat_hashes.size(); i++) {
compatibility.push_back(compat_hashes[i]);
}
}
if (compatibility.size() > 0) {
d2["hash_compatibility"] = compatibility;
}
bool has_return = mi.return_val.type != Variant::NIL || (mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
if (has_return) {
PropertyInfo pinfo = mi.return_val;
@ -1090,7 +1106,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
}
#ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes::get_legacy_hashes(class_name, method_name, compatibility);
GDExtensionSpecialCompatHashes::get_legacy_hashes(class_name, method_name, compatibility);
#endif
if (compatibility.size() > 0) {
@ -1201,7 +1217,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) {
if (F.name.begins_with("_")) {
continue; //hidden property
}
if (F.name.contains("/")) {
if (F.name.contains_char('/')) {
// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
continue;
}
@ -1359,7 +1375,7 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
return true; // May just not have this array and its still good. Probably added recently.
}
bool failed = false;
ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array);
ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, vformat("New API lacks base array: %s", p_base_array));
Array new_api = p_new_api[p_base_array];
HashMap<String, Dictionary> new_api_assoc;
@ -1367,6 +1383,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
Dictionary elem = var;
ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field));
String name = elem[p_name_field];
if (name.is_valid_float()) {
name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
}
if (p_compare_operators && elem.has("right_type")) {
name += " " + String(elem["right_type"]);
}
@ -1382,6 +1401,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
continue;
}
String name = old_elem[p_name_field];
if (name.is_valid_float()) {
name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
}
if (p_compare_operators && old_elem.has("right_type")) {
name += " " + String(old_elem["right_type"]);
}
@ -1463,8 +1485,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
if (p_compare_hashes) {
if (!old_elem.has("hash")) {
if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !new_elem.has("hash")) {
continue; // No hash for virtual methods, go on.
if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !old_elem.has("hash")) {
continue; // Virtual methods didn't use to have hashes, so skip check if it's missing in the old file.
}
failed = true;
@ -1511,7 +1533,7 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered
return true; // May just not have this array and its still good. Probably added recently or optional.
}
bool failed = false;
ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer);
ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, vformat("New API lacks base array: %s", p_outer));
Array new_api = p_new_api[p_outer];
HashMap<String, Dictionary> new_api_assoc;

View file

@ -32,11 +32,9 @@
#include "gdextension.compat.inc"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/object/class_db.h"
#include "core/object/method_bind.h"
#include "core/os/os.h"
#include "core/version.h"
#include "gdextension_library_loader.h"
#include "gdextension_manager.h"
extern void gdextension_setup_interface();
@ -48,146 +46,6 @@ String GDExtension::get_extension_list_config_file() {
return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");
}
Vector<SharedObject> GDExtension::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
Vector<SharedObject> dependencies_shared_objects;
if (p_config->has_section("dependencies")) {
List<String> config_dependencies;
p_config->get_section_keys("dependencies", &config_dependencies);
for (const String &dependency : config_dependencies) {
Vector<String> dependency_tags = dependency.split(".");
bool all_tags_met = true;
for (int i = 0; i < dependency_tags.size(); i++) {
String tag = dependency_tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
if (all_tags_met) {
Dictionary dependency_value = p_config->get_value("dependencies", dependency);
for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
String dependency_path = *key;
String target_path = dependency_value[*key];
if (dependency_path.is_relative_path()) {
dependency_path = p_path.get_base_dir().path_join(dependency_path);
}
dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
}
break;
}
}
}
return dependencies_shared_objects;
}
String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
// First, check the explicit libraries.
if (p_config->has_section("libraries")) {
List<String> libraries;
p_config->get_section_keys("libraries", &libraries);
// Iterate the libraries, finding the best matching tags.
String best_library_path;
Vector<String> best_library_tags;
for (const String &E : libraries) {
Vector<String> tags = E.split(".");
bool all_tags_met = true;
for (int i = 0; i < tags.size(); i++) {
String tag = tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
if (all_tags_met && tags.size() > best_library_tags.size()) {
best_library_path = p_config->get_value("libraries", E);
best_library_tags = tags;
}
}
if (!best_library_path.is_empty()) {
if (best_library_path.is_relative_path()) {
best_library_path = p_path.get_base_dir().path_join(best_library_path);
}
if (r_tags != nullptr) {
r_tags->append_array(best_library_tags);
}
return best_library_path;
}
}
// Second, try to autodetect
String autodetect_library_prefix;
if (p_config->has_section_key("configuration", "autodetect_library_prefix")) {
autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix");
}
if (!autodetect_library_prefix.is_empty()) {
String autodetect_path = autodetect_library_prefix;
if (autodetect_path.is_relative_path()) {
autodetect_path = p_path.get_base_dir().path_join(autodetect_path);
}
// Find the folder and file parts of the prefix.
String folder;
String file_prefix;
if (DirAccess::dir_exists_absolute(autodetect_path)) {
folder = autodetect_path;
} else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) {
folder = autodetect_path.get_base_dir();
file_prefix = autodetect_path.get_file();
} else {
ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix));
}
// Open the folder.
Ref<DirAccess> dir = DirAccess::open(folder);
ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix));
// Iterate the files and check the prefixes, finding the best matching file.
String best_file;
Vector<String> best_file_tags;
dir->list_dir_begin();
String file_name = dir->_get_next();
while (file_name != "") {
if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) {
// Check if the files matches all requested feature tags.
String tags_str = file_name.trim_prefix(file_prefix);
tags_str = tags_str.trim_suffix(tags_str.get_extension());
Vector<String> tags = tags_str.split(".", false);
bool all_tags_met = true;
for (int i = 0; i < tags.size(); i++) {
String tag = tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
// If all tags are found in the feature list, and we found more tags than before, use this file.
if (all_tags_met && tags.size() > best_file_tags.size()) {
best_file_tags = tags;
best_file = file_name;
}
}
file_name = dir->_get_next();
}
if (!best_file.is_empty()) {
String library_path = folder.path_join(best_file);
if (r_tags != nullptr) {
r_tags->append_array(best_file_tags);
}
return library_path;
}
}
return String();
}
class GDExtensionMethodBind : public MethodBind {
GDExtensionClassMethodCall call_func;
GDExtensionClassMethodValidatedCall validated_call_func;
@ -296,7 +154,7 @@ public:
}
virtual bool is_vararg() const override {
return false;
return vararg;
}
#ifdef TOOLS_ENABLED
@ -382,11 +240,12 @@ public:
#ifndef DISABLE_DEPRECATED
void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) {
const GDExtensionClassCreationInfo3 class_info3 = {
const GDExtensionClassCreationInfo4 class_info4 = {
p_extension_funcs->is_virtual, // GDExtensionBool is_virtual;
p_extension_funcs->is_abstract, // GDExtensionBool is_abstract;
true, // GDExtensionBool is_exposed;
false, // GDExtensionBool is_runtime;
nullptr, // GDExtensionConstStringPtr icon_path;
p_extension_funcs->set_func, // GDExtensionClassSet set_func;
p_extension_funcs->get_func, // GDExtensionClassGet get_func;
p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func;
@ -398,29 +257,33 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func;
p_extension_funcs->reference_func, // GDExtensionClassReference reference_func;
p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
nullptr, // GDExtensionClassRecreateInstance recreate_instance_func;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
const ClassCreationDeprecatedInfo legacy = {
p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr,
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy);
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs) {
const GDExtensionClassCreationInfo3 class_info3 = {
const GDExtensionClassCreationInfo4 class_info4 = {
p_extension_funcs->is_virtual, // GDExtensionBool is_virtual;
p_extension_funcs->is_abstract, // GDExtensionBool is_abstract;
p_extension_funcs->is_exposed, // GDExtensionBool is_exposed;
false, // GDExtensionBool is_runtime;
nullptr, // GDExtensionConstStringPtr icon_path;
p_extension_funcs->set_func, // GDExtensionClassSet set_func;
p_extension_funcs->get_func, // GDExtensionClassGet get_func;
p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func;
@ -432,35 +295,77 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func;
p_extension_funcs->reference_func, // GDExtensionClassReference reference_func;
p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
const ClassCreationDeprecatedInfo legacy = {
nullptr, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy);
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
#endif // DISABLE_DEPRECATED
void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs) {
const GDExtensionClassCreationInfo4 class_info4 = {
p_extension_funcs->is_virtual, // GDExtensionBool is_virtual;
p_extension_funcs->is_abstract, // GDExtensionBool is_abstract;
p_extension_funcs->is_exposed, // GDExtensionBool is_exposed;
p_extension_funcs->is_runtime, // GDExtensionBool is_runtime;
nullptr, // GDExtensionConstStringPtr icon_path;
p_extension_funcs->set_func, // GDExtensionClassSet set_func;
p_extension_funcs->get_func, // GDExtensionClassGet get_func;
p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func;
p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func;
p_extension_funcs->validate_property_func, // GDExtensionClassValidateProperty validate_property_func;
p_extension_funcs->notification_func, // GDExtensionClassNotification2 notification_func;
p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func;
p_extension_funcs->reference_func, // GDExtensionClassReference reference_func;
p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func;
nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func;
nullptr, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->class_userdata, // void *class_userdata;
};
const ClassCreationDeprecatedInfo legacy = {
nullptr, // GDExtensionClassNotification notification_func;
nullptr, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
#endif // DISABLE_DEPRECATED
void GDExtension::_register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs) {
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, p_extension_funcs);
}
void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) {
void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) {
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), vformat("Attempt to register extension class '%s', which is not a valid class identifier.", class_name));
ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), vformat("Attempt to register extension class '%s', which appears to be already registered.", class_name));
Extension *parent_extension = nullptr;
@ -474,7 +379,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
//inheriting from engine class
}
} else {
ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'.");
ERR_FAIL_MSG(vformat("Attempt to register an extension class '%s' using non-existing parent class '%s'.", String(class_name), String(parent_class_name)));
}
#ifdef TOOLS_ENABLED
@ -530,6 +435,10 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
if (p_deprecated_funcs) {
extension->gdextension.notification = p_deprecated_funcs->notification_func;
extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func;
extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func;
extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func;
extension->gdextension.get_virtual = p_deprecated_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_deprecated_funcs->get_virtual_call_data_func;
}
#endif // DISABLE_DEPRECATED
extension->gdextension.notification2 = p_extension_funcs->notification_func;
@ -537,13 +446,12 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.reference = p_extension_funcs->reference_func;
extension->gdextension.unreference = p_extension_funcs->unreference_func;
extension->gdextension.class_userdata = p_extension_funcs->class_userdata;
extension->gdextension.create_instance = p_extension_funcs->create_instance_func;
extension->gdextension.create_instance2 = p_extension_funcs->create_instance_func;
extension->gdextension.free_instance = p_extension_funcs->free_instance_func;
extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func;
extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.get_virtual2 = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data2 = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func;
extension->gdextension.get_rid = p_extension_funcs->get_rid_func;
extension->gdextension.reloadable = self->reloadable;
#ifdef TOOLS_ENABLED
@ -559,6 +467,13 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
#endif
ClassDB::register_extension_class(&extension->gdextension);
if (p_extension_funcs->icon_path != nullptr) {
const String icon_path = *reinterpret_cast<const String *>(p_extension_funcs->icon_path);
if (!icon_path.is_empty()) {
self->class_icon_paths[class_name] = icon_path;
}
}
}
void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info) {
@ -566,7 +481,7 @@ void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName method_name = *reinterpret_cast<const StringName *>(p_method_info->name);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension method '%s' for unexisting class '%s'.", String(method_name), class_name));
#ifdef TOOLS_ENABLED
Extension *extension = &self->extension_classes[class_name];
@ -616,7 +531,7 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName enum_name = *reinterpret_cast<const StringName *>(p_enum_name);
StringName constant_name = *reinterpret_cast<const StringName *>(p_constant_name);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension constant '%s' for unexisting class '%s'.", constant_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@ -640,7 +555,7 @@ void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLib
StringName setter = *reinterpret_cast<const StringName *>(p_setter);
StringName getter = *reinterpret_cast<const StringName *>(p_getter);
String property_name = *reinterpret_cast<const StringName *>(p_info->name);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property '%s' for unexisting class '%s'.", property_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@ -661,7 +576,7 @@ void GDExtension::_register_extension_class_property_group(GDExtensionClassLibra
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String group_name = *reinterpret_cast<const String *>(p_group_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property group '%s' for unexisting class '%s'.", group_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@ -680,7 +595,7 @@ void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLi
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String subgroup_name = *reinterpret_cast<const String *>(p_subgroup_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property subgroup '%s' for unexisting class '%s'.", subgroup_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@ -698,7 +613,7 @@ void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName signal_name = *reinterpret_cast<const StringName *>(p_signal_name);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class signal '%s' for unexisting class '%s'.", signal_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@ -721,7 +636,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'.");
ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to unregister unexisting extension class '%s'.", class_name));
Extension *ext = &self->extension_classes[class_name];
#ifdef TOOLS_ENABLED
@ -729,7 +644,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
self->_clear_extension(ext);
}
#endif
ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it.");
ERR_FAIL_COND_MSG(ext->gdextension.children.size(), vformat("Attempt to unregister class '%s' while other extension classes inherit from it.", class_name));
#ifdef TOOLS_ENABLED
ClassDB::unregister_extension_class(class_name, !ext->is_reloading);
@ -755,80 +670,54 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) {
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
memnew_placement(r_path, String(self->library_path));
Ref<GDExtensionLibraryLoader> library_loader = self->loader;
String library_path;
if (library_loader.is_valid()) {
library_path = library_loader->library_path;
}
memnew_placement(r_path, String(library_path));
}
HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions;
void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), vformat("Attempt to register interface function '%s', which appears to be already registered.", p_function_name));
gdextension_interface_functions.insert(p_function_name, p_function_pointer);
}
GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) {
GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + ".");
ERR_FAIL_NULL_V_MSG(function, nullptr, vformat("Attempt to get non-existent interface function: '%s'.", String(p_function_name)));
return *function;
}
Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies) {
String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader.");
loader = p_loader;
Vector<String> abs_dependencies_paths;
if (p_dependencies != nullptr && !p_dependencies->is_empty()) {
for (const SharedObject &dependency : *p_dependencies) {
abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
}
}
Error err = loader->open_library(p_path);
String actual_lib_path;
OS::GDExtensionData data = {
true, // also_set_library_path
&actual_lib_path, // r_resolved_path
Engine::get_singleton()->is_editor_hint(), // generate_temp_files
&abs_dependencies_paths, // library_dependencies
};
Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, vformat("GDExtension dynamic library not found: '%s'.", p_path));
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Can't open GDExtension dynamic library: '%s'.", p_path));
if (actual_lib_path.get_file() != abs_path.get_file()) {
// If temporary files are generated, let's change the library path to point at the original,
// because that's what we want to check to see if it's changed.
library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file());
} else {
library_path = actual_lib_path;
}
ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
void *entry_funcptr = nullptr;
err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, p_entry_symbol, entry_funcptr, false);
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
if (err != OK) {
ERR_PRINT("GDExtension entry point '" + p_entry_symbol + "' not found in library " + abs_path);
OS::get_singleton()->close_dynamic_library(library);
// Errors already logged in initialize().
loader->close_library();
return err;
}
GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr;
GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization);
level_initialized = -1;
if (ret) {
level_initialized = -1;
return OK;
} else {
ERR_PRINT("GDExtension initialization function '" + p_entry_symbol + "' returned an error.");
OS::get_singleton()->close_dynamic_library(library);
return FAILED;
}
return OK;
}
void GDExtension::close_library() {
ERR_FAIL_NULL(library);
OS::get_singleton()->close_dynamic_library(library);
ERR_FAIL_COND(!is_library_open());
loader->close_library();
library = nullptr;
class_icon_paths.clear();
#ifdef TOOLS_ENABLED
@ -837,16 +726,16 @@ void GDExtension::close_library() {
}
bool GDExtension::is_library_open() const {
return library != nullptr;
return loader.is_valid() && loader->is_library_open();
}
GDExtension::InitializationLevel GDExtension::get_minimum_library_initialization_level() const {
ERR_FAIL_NULL_V(library, INITIALIZATION_LEVEL_CORE);
ERR_FAIL_COND_V(!is_library_open(), INITIALIZATION_LEVEL_CORE);
return InitializationLevel(initialization.minimum_initialization_level);
}
void GDExtension::initialize_library(InitializationLevel p_level) {
ERR_FAIL_NULL(library);
ERR_FAIL_COND(!is_library_open());
ERR_FAIL_COND_MSG(p_level <= int32_t(level_initialized), vformat("Level '%d' must be higher than the current level '%d'", p_level, level_initialized));
level_initialized = int32_t(p_level);
@ -856,7 +745,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) {
initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level));
}
void GDExtension::deinitialize_library(InitializationLevel p_level) {
ERR_FAIL_NULL(library);
ERR_FAIL_COND(!is_library_open());
ERR_FAIL_COND(p_level > int32_t(level_initialized));
level_initialized = int32_t(p_level) - 1;
@ -880,7 +769,7 @@ GDExtension::GDExtension() {
}
GDExtension::~GDExtension() {
if (library != nullptr) {
if (is_library_open()) {
close_library();
}
#ifdef TOOLS_ENABLED
@ -897,8 +786,9 @@ void GDExtension::initialize_gdextensions() {
#ifndef DISABLE_DEPRECATED
register_interface_function("classdb_register_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class);
register_interface_function("classdb_register_extension_class2", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class2);
#endif // DISABLE_DEPRECATED
register_interface_function("classdb_register_extension_class3", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class3);
#endif // DISABLE_DEPRECATED
register_interface_function("classdb_register_extension_class4", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class4);
register_interface_function("classdb_register_extension_class_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method);
register_interface_function("classdb_register_extension_class_virtual_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_virtual_method);
register_interface_function("classdb_register_extension_class_integer_constant", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant);
@ -918,142 +808,15 @@ void GDExtension::finalize_gdextensions() {
Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension) {
ERR_FAIL_COND_V_MSG(p_extension.is_valid() && p_extension->is_library_open(), ERR_ALREADY_IN_USE, "Cannot load GDExtension resource into already opened library.");
Ref<ConfigFile> config;
config.instantiate();
GDExtensionManager *extension_manager = GDExtensionManager::get_singleton();
Error err = config->load(p_path);
if (err != OK) {
ERR_PRINT("Error loading GDExtension configuration file: " + p_path);
return err;
}
if (!config->has_section_key("configuration", "entry_symbol")) {
ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path);
return ERR_INVALID_DATA;
}
String entry_symbol = config->get_value("configuration", "entry_symbol");
uint32_t compatibility_minimum[3] = { 0, 0, 0 };
if (config->has_section_key("configuration", "compatibility_minimum")) {
String compat_string = config->get_value("configuration", "compatibility_minimum");
Vector<int> parts = compat_string.split_ints(".");
for (int i = 0; i < parts.size(); i++) {
if (i >= 3) {
break;
}
if (parts[i] >= 0) {
compatibility_minimum[i] = parts[i];
}
}
} else {
ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path);
return ERR_INVALID_DATA;
}
if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) {
ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
return ERR_INVALID_DATA;
}
bool compatible = true;
// Check version lexicographically.
if (VERSION_MAJOR != compatibility_minimum[0]) {
compatible = VERSION_MAJOR > compatibility_minimum[0];
} else if (VERSION_MINOR != compatibility_minimum[1]) {
compatible = VERSION_MINOR > compatibility_minimum[1];
} else {
compatible = VERSION_PATCH >= compatibility_minimum[2];
}
if (!compatible) {
ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
return ERR_INVALID_DATA;
}
// Optionally check maximum compatibility.
if (config->has_section_key("configuration", "compatibility_maximum")) {
uint32_t compatibility_maximum[3] = { 0, 0, 0 };
String compat_string = config->get_value("configuration", "compatibility_maximum");
Vector<int> parts = compat_string.split_ints(".");
for (int i = 0; i < 3; i++) {
if (i < parts.size() && parts[i] >= 0) {
compatibility_maximum[i] = parts[i];
} else {
// If a version part is missing, set the maximum to an arbitrary high value.
compatibility_maximum[i] = 9999;
}
}
compatible = true;
if (VERSION_MAJOR != compatibility_maximum[0]) {
compatible = VERSION_MAJOR < compatibility_maximum[0];
} else if (VERSION_MINOR != compatibility_maximum[1]) {
compatible = VERSION_MINOR < compatibility_maximum[1];
}
#if VERSION_PATCH
// #if check to avoid -Wtype-limits warning when 0.
else {
compatible = VERSION_PATCH <= compatibility_maximum[2];
}
#endif
if (!compatible) {
ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path));
return ERR_INVALID_DATA;
}
}
String library_path = GDExtension::find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
if (library_path.is_empty()) {
const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name();
ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path));
return ERR_FILE_NOT_FOUND;
}
bool is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework");
if (!library_path.is_resource_file() && !library_path.is_absolute_path()) {
library_path = p_path.get_base_dir().path_join(library_path);
}
if (p_extension.is_null()) {
p_extension.instantiate();
}
#ifdef TOOLS_ENABLED
p_extension->set_reloadable(config->get_value("configuration", "reloadable", false) && Engine::get_singleton()->is_extension_reloading_enabled());
p_extension->update_last_modified_time(
FileAccess::get_modified_time(p_path),
FileAccess::get_modified_time(library_path));
#endif
Vector<SharedObject> library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies);
if (err != OK) {
// Unreference the extension so that this loading can be considered a failure.
p_extension.unref();
// Errors already logged in open_library()
return err;
}
// Handle icons if any are specified.
if (config->has_section("icons")) {
List<String> keys;
config->get_section_keys("icons", &keys);
for (const String &key : keys) {
String icon_path = config->get_value("icons", key);
if (icon_path.is_relative_path()) {
icon_path = p_path.get_base_dir().path_join(icon_path);
}
p_extension->class_icon_paths[key] = icon_path;
}
GDExtensionManager::LoadStatus status = extension_manager->load_extension(p_path);
if (status != GDExtensionManager::LOAD_STATUS_OK && status != GDExtensionManager::LOAD_STATUS_ALREADY_LOADED) {
// Errors already logged in load_extension().
return FAILED;
}
p_extension = extension_manager->get_extension(p_path);
return OK;
}
@ -1094,16 +857,7 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const
#ifdef TOOLS_ENABLED
bool GDExtension::has_library_changed() const {
// Check only that the last modified time is different (rather than checking
// that it's newer) since some OS's (namely Windows) will preserve the modified
// time by default when copying files.
if (FileAccess::get_modified_time(get_path()) != resource_last_modified_time) {
return true;
}
if (FileAccess::get_modified_time(library_path) != library_last_modified_time) {
return true;
}
return false;
return loader->has_library_changed();
}
void GDExtension::prepare_reload() {

View file

@ -31,13 +31,11 @@
#ifndef GDEXTENSION_H
#define GDEXTENSION_H
#include <functional>
#include "core/extension/gdextension_interface.h"
#include "core/extension/gdextension_loader.h"
#include "core/io/config_file.h"
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/os/shared_object.h"
class GDExtensionMethodBind;
@ -46,8 +44,8 @@ class GDExtension : public Resource {
friend class GDExtensionManager;
void *library = nullptr; // pointer if valid,
String library_path;
Ref<GDExtensionLoader> loader;
bool reloadable = false;
struct Extension {
@ -72,15 +70,20 @@ class GDExtension : public Resource {
#ifndef DISABLE_DEPRECATED
GDExtensionClassNotification notification_func = nullptr;
GDExtensionClassFreePropertyList free_property_list_func = nullptr;
GDExtensionClassCreateInstance create_instance_func = nullptr;
GDExtensionClassGetRID get_rid_func = nullptr;
GDExtensionClassGetVirtual get_virtual_func = nullptr;
GDExtensionClassGetVirtualCallData get_virtual_call_data_func = nullptr;
#endif // DISABLE_DEPRECATED
};
#ifndef DISABLE_DEPRECATED
static void _register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
static void _register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs);
#endif // DISABLE_DEPRECATED
static void _register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs);
static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr);
#endif // DISABLE_DEPRECATED
static void _register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs);
static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr);
static void _register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
static void _register_extension_class_virtual_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info);
static void _register_extension_class_integer_constant(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield);
@ -96,8 +99,6 @@ class GDExtension : public Resource {
int32_t level_initialized = -1;
#ifdef TOOLS_ENABLED
uint64_t resource_last_modified_time = 0;
uint64_t library_last_modified_time = 0;
bool is_reloading = false;
Vector<GDExtensionMethodBind *> invalid_methods;
Vector<ObjectID> instance_bindings;
@ -124,11 +125,12 @@ public:
virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way.
static String get_extension_list_config_file();
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);
Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr);
const Ref<GDExtensionLoader> get_loader() const { return loader; }
Error open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader);
void close_library();
bool is_library_open() const;
enum InitializationLevel {
INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
@ -146,17 +148,11 @@ protected:
#endif
public:
bool is_library_open() const;
#ifdef TOOLS_ENABLED
bool is_reloadable() const { return reloadable; }
void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; }
bool has_library_changed() const;
void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) {
resource_last_modified_time = p_resource_last_modified_time;
library_last_modified_time = p_library_last_modified_time;
}
void track_instance_binding(Object *p_object);
void untrack_instance_binding(Object *p_object);

View file

@ -32,8 +32,9 @@
#include "core/config/engine.h"
#include "core/extension/gdextension.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_special_compat_hashes.h"
#include "core/io/file_access.h"
#include "core/io/image.h"
#include "core/io/xml_parser.h"
#include "core/object/class_db.h"
#include "core/object/script_language_extension.h"
@ -507,6 +508,14 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_
return ret;
}
static GDObjectInstanceID gdextension_variant_get_object_instance_id(GDExtensionConstVariantPtr p_self) {
const Variant *self = (const Variant *)p_self;
if (likely(self->get_type() == Variant::OBJECT)) {
return self->operator ObjectID();
}
return 0;
}
static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) {
String name = Variant::get_type_name((Variant::Type)p_type);
memnew_placement(r_ret, String(name));
@ -691,6 +700,91 @@ static GDExtensionTypeFromVariantConstructorFunc gdextension_get_variant_to_type
ERR_FAIL_V_MSG(nullptr, "Getting Variant conversion function with invalid type");
}
static GDExtensionVariantGetInternalPtrFunc gdextension_variant_get_ptr_internal_getter(GDExtensionVariantType p_type) {
switch (p_type) {
case GDEXTENSION_VARIANT_TYPE_BOOL:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<bool *(*)(Variant *)>(VariantInternal::get_bool));
case GDEXTENSION_VARIANT_TYPE_INT:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<int64_t *(*)(Variant *)>(VariantInternal::get_int));
case GDEXTENSION_VARIANT_TYPE_FLOAT:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<double *(*)(Variant *)>(VariantInternal::get_float));
case GDEXTENSION_VARIANT_TYPE_STRING:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<String *(*)(Variant *)>(VariantInternal::get_string));
case GDEXTENSION_VARIANT_TYPE_VECTOR2:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector2 *(*)(Variant *)>(VariantInternal::get_vector2));
case GDEXTENSION_VARIANT_TYPE_VECTOR2I:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector2i *(*)(Variant *)>(VariantInternal::get_vector2i));
case GDEXTENSION_VARIANT_TYPE_RECT2:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Rect2 *(*)(Variant *)>(VariantInternal::get_rect2));
case GDEXTENSION_VARIANT_TYPE_RECT2I:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Rect2i *(*)(Variant *)>(VariantInternal::get_rect2i));
case GDEXTENSION_VARIANT_TYPE_VECTOR3:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector3 *(*)(Variant *)>(VariantInternal::get_vector3));
case GDEXTENSION_VARIANT_TYPE_VECTOR3I:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector3i *(*)(Variant *)>(VariantInternal::get_vector3i));
case GDEXTENSION_VARIANT_TYPE_TRANSFORM2D:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Transform2D *(*)(Variant *)>(VariantInternal::get_transform2d));
case GDEXTENSION_VARIANT_TYPE_VECTOR4:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector4 *(*)(Variant *)>(VariantInternal::get_vector4));
case GDEXTENSION_VARIANT_TYPE_VECTOR4I:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Vector4i *(*)(Variant *)>(VariantInternal::get_vector4i));
case GDEXTENSION_VARIANT_TYPE_PLANE:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Plane *(*)(Variant *)>(VariantInternal::get_plane));
case GDEXTENSION_VARIANT_TYPE_QUATERNION:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Quaternion *(*)(Variant *)>(VariantInternal::get_quaternion));
case GDEXTENSION_VARIANT_TYPE_AABB:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<AABB *(*)(Variant *)>(VariantInternal::get_aabb));
case GDEXTENSION_VARIANT_TYPE_BASIS:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Basis *(*)(Variant *)>(VariantInternal::get_basis));
case GDEXTENSION_VARIANT_TYPE_TRANSFORM3D:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Transform3D *(*)(Variant *)>(VariantInternal::get_transform));
case GDEXTENSION_VARIANT_TYPE_PROJECTION:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Projection *(*)(Variant *)>(VariantInternal::get_projection));
case GDEXTENSION_VARIANT_TYPE_COLOR:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Color *(*)(Variant *)>(VariantInternal::get_color));
case GDEXTENSION_VARIANT_TYPE_STRING_NAME:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<StringName *(*)(Variant *)>(VariantInternal::get_string_name));
case GDEXTENSION_VARIANT_TYPE_NODE_PATH:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<NodePath *(*)(Variant *)>(VariantInternal::get_node_path));
case GDEXTENSION_VARIANT_TYPE_RID:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<RID *(*)(Variant *)>(VariantInternal::get_rid));
case GDEXTENSION_VARIANT_TYPE_OBJECT:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Object **(*)(Variant *)>(VariantInternal::get_object));
case GDEXTENSION_VARIANT_TYPE_CALLABLE:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Callable *(*)(Variant *)>(VariantInternal::get_callable));
case GDEXTENSION_VARIANT_TYPE_SIGNAL:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Signal *(*)(Variant *)>(VariantInternal::get_signal));
case GDEXTENSION_VARIANT_TYPE_DICTIONARY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Dictionary *(*)(Variant *)>(VariantInternal::get_dictionary));
case GDEXTENSION_VARIANT_TYPE_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<Array *(*)(Variant *)>(VariantInternal::get_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_BYTE_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedByteArray *(*)(Variant *)>(VariantInternal::get_byte_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_INT32_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedInt32Array *(*)(Variant *)>(VariantInternal::get_int32_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_INT64_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedInt64Array *(*)(Variant *)>(VariantInternal::get_int64_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_FLOAT32_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedFloat32Array *(*)(Variant *)>(VariantInternal::get_float32_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_FLOAT64_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedFloat64Array *(*)(Variant *)>(VariantInternal::get_float64_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_STRING_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedStringArray *(*)(Variant *)>(VariantInternal::get_string_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector2Array *(*)(Variant *)>(VariantInternal::get_vector2_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR3_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector3Array *(*)(Variant *)>(VariantInternal::get_vector3_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_COLOR_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedColorArray *(*)(Variant *)>(VariantInternal::get_color_array));
case GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR4_ARRAY:
return reinterpret_cast<GDExtensionVariantGetInternalPtrFunc>(static_cast<PackedVector4Array *(*)(Variant *)>(VariantInternal::get_vector4_array));
case GDEXTENSION_VARIANT_TYPE_NIL:
case GDEXTENSION_VARIANT_TYPE_VARIANT_MAX:
ERR_FAIL_V_MSG(nullptr, "Getting Variant get internal pointer function with invalid type.");
}
ERR_FAIL_V_MSG(nullptr, "Getting Variant get internal pointer function with invalid type.");
}
// ptrcalls
static GDExtensionPtrOperatorEvaluator gdextension_variant_get_ptr_operator_evaluator(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b) {
return (GDExtensionPtrOperatorEvaluator)Variant::get_ptr_operator_evaluator(Variant::Operator(p_operator), Variant::Type(p_type_a), Variant::Type(p_type_b));
@ -1199,6 +1293,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten
return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key);
}
void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) {
Dictionary *self = reinterpret_cast<Dictionary *>(p_self);
const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name);
const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script);
const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name);
const Variant *value_script = reinterpret_cast<const Variant *>(p_value_script);
self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script);
}
/* OBJECT API */
static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
@ -1299,7 +1402,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object,
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
Callable::CallError error;
Callable::CallError error; // TODO: Check `error`?
memnew_placement(r_return, Variant);
*(Variant *)r_return = o->callp(method, args, p_argument_count, error);
@ -1501,24 +1604,31 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC
// If lookup failed, see if this is one of the broken hashes from issue #81386.
if (!mb && exists) {
uint32_t mapped_hash;
if (GDExtensionCompatHashes::lookup_current_hash(classname, methodname, p_hash, &mapped_hash)) {
if (GDExtensionSpecialCompatHashes::lookup_current_hash(classname, methodname, p_hash, &mapped_hash)) {
mb = ClassDB::get_method_with_compatibility(classname, methodname, mapped_hash, &exists);
}
}
#endif
if (!mb && exists) {
ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue.");
ERR_PRINT(vformat("Method '%s.%s' has changed and no compatibility fallback has been provided. Please open an issue.", classname, methodname));
return nullptr;
}
ERR_FAIL_NULL_V(mb, nullptr);
return (GDExtensionMethodBindPtr)mb;
}
#ifndef DISABLE_DEPRECATED
static GDExtensionObjectPtr gdextension_classdb_construct_object(GDExtensionConstStringNamePtr p_classname) {
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
return (GDExtensionObjectPtr)ClassDB::instantiate_no_placeholders(classname);
}
#endif
static GDExtensionObjectPtr gdextension_classdb_construct_object2(GDExtensionConstStringNamePtr p_classname) {
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
return (GDExtensionObjectPtr)ClassDB::instantiate_without_postinitialization(classname);
}
static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_classname) {
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
@ -1594,11 +1704,13 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(variant_has_method);
REGISTER_INTERFACE_FUNC(variant_has_member);
REGISTER_INTERFACE_FUNC(variant_has_key);
REGISTER_INTERFACE_FUNC(variant_get_object_instance_id);
REGISTER_INTERFACE_FUNC(variant_get_type_name);
REGISTER_INTERFACE_FUNC(variant_can_convert);
REGISTER_INTERFACE_FUNC(variant_can_convert_strict);
REGISTER_INTERFACE_FUNC(get_variant_from_type_constructor);
REGISTER_INTERFACE_FUNC(get_variant_to_type_constructor);
REGISTER_INTERFACE_FUNC(variant_get_ptr_internal_getter);
REGISTER_INTERFACE_FUNC(variant_get_ptr_operator_evaluator);
REGISTER_INTERFACE_FUNC(variant_get_ptr_builtin_method);
REGISTER_INTERFACE_FUNC(variant_get_ptr_constructor);
@ -1672,6 +1784,7 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(array_set_typed);
REGISTER_INTERFACE_FUNC(dictionary_operator_index);
REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
REGISTER_INTERFACE_FUNC(dictionary_set_typed);
REGISTER_INTERFACE_FUNC(object_method_bind_call);
REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
REGISTER_INTERFACE_FUNC(object_destroy);
@ -1701,7 +1814,10 @@ void gdextension_setup_interface() {
#endif // DISABLE_DEPRECATED
REGISTER_INTERFACE_FUNC(callable_custom_create2);
REGISTER_INTERFACE_FUNC(callable_custom_get_userdata);
#ifndef DISABLE_DEPRECATED
REGISTER_INTERFACE_FUNC(classdb_construct_object);
#endif // DISABLE_DEPRECATED
REGISTER_INTERFACE_FUNC(classdb_construct_object2);
REGISTER_INTERFACE_FUNC(classdb_get_method_bind);
REGISTER_INTERFACE_FUNC(classdb_get_class_tag);
REGISTER_INTERFACE_FUNC(editor_add_plugin);

View file

@ -198,6 +198,7 @@ typedef struct {
typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr);
typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr);
typedef void *(*GDExtensionVariantGetInternalPtrFunc)(GDExtensionVariantPtr);
typedef void (*GDExtensionPtrOperatorEvaluator)(GDExtensionConstTypePtr p_left, GDExtensionConstTypePtr p_right, GDExtensionTypePtr r_result);
typedef void (*GDExtensionPtrBuiltInMethod)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_return, int p_argument_count);
typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args);
@ -268,10 +269,13 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance
typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata);
typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize);
typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash);
typedef void *(*GDExtensionClassGetVirtualCallData)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
typedef void *(*GDExtensionClassGetVirtualCallData2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash);
typedef void (*GDExtensionClassCallVirtualWithData)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef struct {
@ -292,7 +296,7 @@ typedef struct {
GDExtensionClassGetVirtual get_virtual_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function.
GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo3 instead.
} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo4 instead.
typedef struct {
GDExtensionBool is_virtual;
@ -325,7 +329,7 @@ typedef struct {
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo3 instead.
} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo4 instead.
typedef struct {
GDExtensionBool is_virtual;
@ -359,7 +363,41 @@ typedef struct {
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo3;
} GDExtensionClassCreationInfo3; // Deprecated. Use GDExtensionClassCreationInfo4 instead.
typedef struct {
GDExtensionBool is_virtual;
GDExtensionBool is_abstract;
GDExtensionBool is_exposed;
GDExtensionBool is_runtime;
GDExtensionConstStringPtr icon_path;
GDExtensionClassSet set_func;
GDExtensionClassGet get_func;
GDExtensionClassGetPropertyList get_property_list_func;
GDExtensionClassFreePropertyList2 free_property_list_func;
GDExtensionClassPropertyCanRevert property_can_revert_func;
GDExtensionClassPropertyGetRevert property_get_revert_func;
GDExtensionClassValidateProperty validate_property_func;
GDExtensionClassNotification2 notification_func;
GDExtensionClassToString to_string_func;
GDExtensionClassReference reference_func;
GDExtensionClassUnreference unreference_func;
GDExtensionClassCreateInstance2 create_instance_func; // (Default) constructor; mandatory. If the class is not instantiable, consider making it virtual or abstract.
GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory.
GDExtensionClassRecreateInstance recreate_instance_func;
// Queries a virtual function by name and returns a callback to invoke the requested virtual function.
GDExtensionClassGetVirtual2 get_virtual_func;
// Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that
// need or benefit from extra data when calling virtual functions.
// Returns user data that will be passed to `call_virtual_with_data_func`.
// Returning `NULL` from this function signals to Godot that the virtual function is not overridden.
// Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized.
// You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`.
GDExtensionClassGetVirtualCallData2 get_virtual_call_data_func;
// Used to call virtual functions when `get_virtual_call_data_func` is not null.
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo4;
typedef void *GDExtensionClassLibraryPtr;
@ -386,7 +424,9 @@ typedef enum {
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT32,
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT64,
GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_FLOAT,
GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE
GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE,
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR16,
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR32,
} GDExtensionClassMethodArgumentMetadata;
typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
@ -805,7 +845,7 @@ typedef void (*GDExtensionInterfaceMemFree)(void *p_ptr);
*
* Logs an error to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the error.
* @param p_description The code triggering the error.
* @param p_function The function name where the error occurred.
* @param p_file The file where the error occurred.
* @param p_line The line where the error occurred.
@ -819,7 +859,7 @@ typedef void (*GDExtensionInterfacePrintError)(const char *p_description, const
*
* Logs an error with a message to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the error.
* @param p_description The code triggering the error.
* @param p_message The message to show along with the error.
* @param p_function The function name where the error occurred.
* @param p_file The file where the error occurred.
@ -834,7 +874,7 @@ typedef void (*GDExtensionInterfacePrintErrorWithMessage)(const char *p_descript
*
* Logs a warning to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the warning.
* @param p_description The code triggering the warning.
* @param p_function The function name where the warning occurred.
* @param p_file The file where the warning occurred.
* @param p_line The line where the warning occurred.
@ -848,7 +888,7 @@ typedef void (*GDExtensionInterfacePrintWarning)(const char *p_description, cons
*
* Logs a warning with a message to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the warning.
* @param p_description The code triggering the warning.
* @param p_message The message to show along with the warning.
* @param p_function The function name where the warning occurred.
* @param p_file The file where the warning occurred.
@ -863,7 +903,7 @@ typedef void (*GDExtensionInterfacePrintWarningWithMessage)(const char *p_descri
*
* Logs a script error to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the error.
* @param p_description The code triggering the error.
* @param p_function The function name where the error occurred.
* @param p_file The file where the error occurred.
* @param p_line The line where the error occurred.
@ -877,7 +917,7 @@ typedef void (*GDExtensionInterfacePrintScriptError)(const char *p_description,
*
* Logs a script error with a message to Godot's built-in debugger and to the OS terminal.
*
* @param p_description The code trigging the error.
* @param p_description The code triggering the error.
* @param p_message The message to show along with the error.
* @param p_function The function name where the error occurred.
* @param p_file The file where the error occurred.
@ -1271,6 +1311,21 @@ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVaria
*/
typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid);
/**
* @name variant_get_object_instance_id
* @since 4.4
*
* Gets the object instance ID from a variant of type GDEXTENSION_VARIANT_TYPE_OBJECT.
*
* If the variant isn't of type GDEXTENSION_VARIANT_TYPE_OBJECT, then zero will be returned.
* The instance ID will be returned even if the object is no longer valid - use `object_get_instance_by_id()` to check if the object is still valid.
*
* @param p_self A pointer to the Variant.
*
* @return The instance ID for the contained object.
*/
typedef GDObjectInstanceID (*GDExtensionInterfaceVariantGetObjectInstanceId)(GDExtensionConstVariantPtr p_self);
/**
* @name variant_get_type_name
* @since 4.1
@ -1332,6 +1387,23 @@ typedef GDExtensionVariantFromTypeConstructorFunc (*GDExtensionInterfaceGetVaria
*/
typedef GDExtensionTypeFromVariantConstructorFunc (*GDExtensionInterfaceGetVariantToTypeConstructor)(GDExtensionVariantType p_type);
/**
* @name variant_get_ptr_internal_getter
* @since 4.4
*
* Provides a function pointer for retrieving a pointer to a variant's internal value.
* Access to a variant's internal value can be used to modify it in-place, or to retrieve its value without the overhead of variant conversion functions.
* It is recommended to cache the getter for all variant types in a function table to avoid retrieval overhead upon use.
*
* @note Each function assumes the variant's type has already been determined and matches the function.
* Invoking the function with a variant of a mismatched type has undefined behavior, and may lead to a segmentation fault.
*
* @param p_type The Variant type.
*
* @return A pointer to a type-specific function that returns a pointer to the internal value of a variant. Check the implementation of this function (gdextension_variant_get_ptr_internal_getter) for pointee type info of each variant type.
*/
typedef GDExtensionVariantGetInternalPtrFunc (*GDExtensionInterfaceGetVariantGetInternalPtrFunc)(GDExtensionVariantType p_type);
/**
* @name variant_get_ptr_operator_evaluator
* @since 4.1
@ -2337,6 +2409,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE
*/
typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
/**
* @name dictionary_set_typed
* @since 4.4
*
* Makes a Dictionary into a typed Dictionary.
*
* @param p_self A pointer to the Dictionary.
* @param p_key_type The type of Variant the Dictionary key will store.
* @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
* @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
* @param p_value_type The type of Variant the Dictionary value will store.
* @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
* @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
*/
typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script);
/* INTERFACE: Object */
/**
@ -2680,6 +2768,7 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT
/**
* @name classdb_construct_object
* @since 4.1
* @deprecated in Godot 4.4. Use `classdb_construct_object2` instead.
*
* Constructs an Object of the requested class.
*
@ -2691,6 +2780,22 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT
*/
typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject)(GDExtensionConstStringNamePtr p_classname);
/**
* @name classdb_construct_object2
* @since 4.4
*
* Constructs an Object of the requested class.
*
* The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object.
*
* "NOTIFICATION_POSTINITIALIZE" must be sent after construction.
*
* @param p_classname A pointer to a StringName with the class name.
*
* @return A pointer to the newly created Object.
*/
typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject2)(GDExtensionConstStringNamePtr p_classname);
/**
* @name classdb_get_method_bind
* @since 4.1
@ -2722,7 +2827,7 @@ typedef void *(*GDExtensionInterfaceClassdbGetClassTag)(GDExtensionConstStringNa
/**
* @name classdb_register_extension_class
* @since 4.1
* @deprecated in Godot 4.2. Use `classdb_register_extension_class3` instead.
* @deprecated in Godot 4.2. Use `classdb_register_extension_class4` instead.
*
* Registers an extension class in the ClassDB.
*
@ -2738,7 +2843,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionCla
/**
* @name classdb_register_extension_class2
* @since 4.2
* @deprecated in Godot 4.3. Use `classdb_register_extension_class3` instead.
* @deprecated in Godot 4.3. Use `classdb_register_extension_class4` instead.
*
* Registers an extension class in the ClassDB.
*
@ -2754,6 +2859,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl
/**
* @name classdb_register_extension_class3
* @since 4.3
* @deprecated in Godot 4.4. Use `classdb_register_extension_class4` instead.
*
* Registers an extension class in the ClassDB.
*
@ -2766,6 +2872,21 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl
*/
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass3)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs);
/**
* @name classdb_register_extension_class4
* @since 4.4
*
* Registers an extension class in the ClassDB.
*
* Provided struct can be safely freed once the function returns.
*
* @param p_library A pointer the library received by the GDExtension's entry point function.
* @param p_class_name A pointer to a StringName with the class name.
* @param p_parent_class_name A pointer to a StringName with the parent class name.
* @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo2 struct.
*/
typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass4)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs);
/**
* @name classdb_register_extension_class_method
* @since 4.1

View file

@ -0,0 +1,394 @@
/**************************************************************************/
/* gdextension_library_loader.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdextension_library_loader.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/version.h"
#include "gdextension.h"
Vector<SharedObject> GDExtensionLibraryLoader::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
Vector<SharedObject> dependencies_shared_objects;
if (p_config->has_section("dependencies")) {
List<String> config_dependencies;
p_config->get_section_keys("dependencies", &config_dependencies);
for (const String &dependency : config_dependencies) {
Vector<String> dependency_tags = dependency.split(".");
bool all_tags_met = true;
for (int i = 0; i < dependency_tags.size(); i++) {
String tag = dependency_tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
if (all_tags_met) {
Dictionary dependency_value = p_config->get_value("dependencies", dependency);
for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
String dependency_path = *key;
String target_path = dependency_value[*key];
if (dependency_path.is_relative_path()) {
dependency_path = p_path.get_base_dir().path_join(dependency_path);
}
dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
}
break;
}
}
}
return dependencies_shared_objects;
}
String GDExtensionLibraryLoader::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
// First, check the explicit libraries.
if (p_config->has_section("libraries")) {
List<String> libraries;
p_config->get_section_keys("libraries", &libraries);
// Iterate the libraries, finding the best matching tags.
String best_library_path;
Vector<String> best_library_tags;
for (const String &E : libraries) {
Vector<String> tags = E.split(".");
bool all_tags_met = true;
for (int i = 0; i < tags.size(); i++) {
String tag = tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
if (all_tags_met && tags.size() > best_library_tags.size()) {
best_library_path = p_config->get_value("libraries", E);
best_library_tags = tags;
}
}
if (!best_library_path.is_empty()) {
if (best_library_path.is_relative_path()) {
best_library_path = p_path.get_base_dir().path_join(best_library_path);
}
if (r_tags != nullptr) {
r_tags->append_array(best_library_tags);
}
return best_library_path;
}
}
// Second, try to autodetect.
String autodetect_library_prefix;
if (p_config->has_section_key("configuration", "autodetect_library_prefix")) {
autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix");
}
if (!autodetect_library_prefix.is_empty()) {
String autodetect_path = autodetect_library_prefix;
if (autodetect_path.is_relative_path()) {
autodetect_path = p_path.get_base_dir().path_join(autodetect_path);
}
// Find the folder and file parts of the prefix.
String folder;
String file_prefix;
if (DirAccess::dir_exists_absolute(autodetect_path)) {
folder = autodetect_path;
} else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) {
folder = autodetect_path.get_base_dir();
file_prefix = autodetect_path.get_file();
} else {
ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix));
}
// Open the folder.
Ref<DirAccess> dir = DirAccess::open(folder);
ERR_FAIL_COND_V_MSG(dir.is_null(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix));
// Iterate the files and check the prefixes, finding the best matching file.
String best_file;
Vector<String> best_file_tags;
dir->list_dir_begin();
String file_name = dir->_get_next();
while (file_name != "") {
if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) {
// Check if the files matches all requested feature tags.
String tags_str = file_name.trim_prefix(file_prefix);
tags_str = tags_str.trim_suffix(tags_str.get_extension());
Vector<String> tags = tags_str.split(".", false);
bool all_tags_met = true;
for (int i = 0; i < tags.size(); i++) {
String tag = tags[i].strip_edges();
if (!p_has_feature(tag)) {
all_tags_met = false;
break;
}
}
// If all tags are found in the feature list, and we found more tags than before, use this file.
if (all_tags_met && tags.size() > best_file_tags.size()) {
best_file_tags = tags;
best_file = file_name;
}
}
file_name = dir->_get_next();
}
if (!best_file.is_empty()) {
String library_path = folder.path_join(best_file);
if (r_tags != nullptr) {
r_tags->append_array(best_file_tags);
}
return library_path;
}
}
return String();
}
Error GDExtensionLibraryLoader::open_library(const String &p_path) {
Error err = parse_gdextension_file(p_path);
if (err != OK) {
return err;
}
String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path);
Vector<String> abs_dependencies_paths;
if (!library_dependencies.is_empty()) {
for (const SharedObject &dependency : library_dependencies) {
abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
}
}
OS::GDExtensionData data = {
true, // also_set_library_path
&library_path, // r_resolved_path
Engine::get_singleton()->is_editor_hint(), // generate_temp_files
&abs_dependencies_paths, // library_dependencies
};
err = OS::get_singleton()->open_dynamic_library(is_static_library ? String() : abs_path, library, &data);
if (err != OK) {
return err;
}
return OK;
}
Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) {
#ifdef TOOLS_ENABLED
p_extension->set_reloadable(is_reloadable && Engine::get_singleton()->is_extension_reloading_enabled());
#endif
for (const KeyValue<String, String> &icon : class_icon_paths) {
p_extension->class_icon_paths[icon.key] = icon.value;
}
void *entry_funcptr = nullptr;
Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false);
if (err != OK) {
ERR_PRINT(vformat("GDExtension entry point '%s' not found in library %s.", entry_symbol, library_path));
return err;
}
GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr;
GDExtensionBool ret = initialization_function(p_get_proc_address, p_extension.ptr(), r_initialization);
if (ret) {
return OK;
} else {
ERR_PRINT(vformat("GDExtension initialization function '%s' returned an error.", entry_symbol));
return FAILED;
}
}
void GDExtensionLibraryLoader::close_library() {
OS::get_singleton()->close_dynamic_library(library);
library = nullptr;
}
bool GDExtensionLibraryLoader::is_library_open() const {
return library != nullptr;
}
bool GDExtensionLibraryLoader::has_library_changed() const {
#ifdef TOOLS_ENABLED
// Check only that the last modified time is different (rather than checking
// that it's newer) since some OS's (namely Windows) will preserve the modified
// time by default when copying files.
if (FileAccess::get_modified_time(resource_path) != resource_last_modified_time) {
return true;
}
if (FileAccess::get_modified_time(library_path) != library_last_modified_time) {
return true;
}
#endif
return false;
}
bool GDExtensionLibraryLoader::library_exists() const {
return FileAccess::exists(resource_path);
}
Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
resource_path = p_path;
Ref<ConfigFile> config;
config.instantiate();
Error err = config->load(p_path);
if (err != OK) {
ERR_PRINT(vformat("Error loading GDExtension configuration file: '%s'.", p_path));
return err;
}
if (!config->has_section_key("configuration", "entry_symbol")) {
ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
entry_symbol = config->get_value("configuration", "entry_symbol");
uint32_t compatibility_minimum[3] = { 0, 0, 0 };
if (config->has_section_key("configuration", "compatibility_minimum")) {
String compat_string = config->get_value("configuration", "compatibility_minimum");
Vector<int> parts = compat_string.split_ints(".");
for (int i = 0; i < parts.size(); i++) {
if (i >= 3) {
break;
}
if (parts[i] >= 0) {
compatibility_minimum[i] = parts[i];
}
}
} else {
ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) {
ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
return ERR_INVALID_DATA;
}
bool compatible = true;
// Check version lexicographically.
if (VERSION_MAJOR != compatibility_minimum[0]) {
compatible = VERSION_MAJOR > compatibility_minimum[0];
} else if (VERSION_MINOR != compatibility_minimum[1]) {
compatible = VERSION_MINOR > compatibility_minimum[1];
} else {
compatible = VERSION_PATCH >= compatibility_minimum[2];
}
if (!compatible) {
ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
return ERR_INVALID_DATA;
}
// Optionally check maximum compatibility.
if (config->has_section_key("configuration", "compatibility_maximum")) {
uint32_t compatibility_maximum[3] = { 0, 0, 0 };
String compat_string = config->get_value("configuration", "compatibility_maximum");
Vector<int> parts = compat_string.split_ints(".");
for (int i = 0; i < 3; i++) {
if (i < parts.size() && parts[i] >= 0) {
compatibility_maximum[i] = parts[i];
} else {
// If a version part is missing, set the maximum to an arbitrary high value.
compatibility_maximum[i] = 9999;
}
}
compatible = true;
if (VERSION_MAJOR != compatibility_maximum[0]) {
compatible = VERSION_MAJOR < compatibility_maximum[0];
} else if (VERSION_MINOR != compatibility_maximum[1]) {
compatible = VERSION_MINOR < compatibility_maximum[1];
}
#if VERSION_PATCH
// #if check to avoid -Wtype-limits warning when 0.
else {
compatible = VERSION_PATCH <= compatibility_maximum[2];
}
#endif
if (!compatible) {
ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path));
return ERR_INVALID_DATA;
}
}
library_path = find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
if (library_path.is_empty()) {
const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name();
ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path));
return ERR_FILE_NOT_FOUND;
}
is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework");
if (!library_path.is_resource_file() && !library_path.is_absolute_path()) {
library_path = p_path.get_base_dir().path_join(library_path);
}
#ifdef TOOLS_ENABLED
is_reloadable = config->get_value("configuration", "reloadable", false);
update_last_modified_time(
FileAccess::get_modified_time(resource_path),
FileAccess::get_modified_time(library_path));
#endif
library_dependencies = find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
// Handle icons if any are specified.
if (config->has_section("icons")) {
List<String> keys;
config->get_section_keys("icons", &keys);
for (const String &key : keys) {
String icon_path = config->get_value("icons", key);
if (icon_path.is_relative_path()) {
icon_path = p_path.get_base_dir().path_join(icon_path);
}
class_icon_paths[key] = icon_path;
}
}
return OK;
}

View file

@ -0,0 +1,85 @@
/**************************************************************************/
/* gdextension_library_loader.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDEXTENSION_LIBRARY_LOADER_H
#define GDEXTENSION_LIBRARY_LOADER_H
#include <functional>
#include "core/extension/gdextension_loader.h"
#include "core/io/config_file.h"
#include "core/os/shared_object.h"
class GDExtensionLibraryLoader : public GDExtensionLoader {
friend class GDExtensionManager;
friend class GDExtension;
private:
String resource_path;
void *library = nullptr; // pointer if valid.
String library_path;
String entry_symbol;
bool is_static_library = false;
#ifdef TOOLS_ENABLED
bool is_reloadable = false;
#endif
Vector<SharedObject> library_dependencies;
HashMap<String, String> class_icon_paths;
#ifdef TOOLS_ENABLED
uint64_t resource_last_modified_time = 0;
uint64_t library_last_modified_time = 0;
void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) {
resource_last_modified_time = p_resource_last_modified_time;
library_last_modified_time = p_library_last_modified_time;
}
#endif
public:
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);
virtual Error open_library(const String &p_path) override;
virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) override;
virtual void close_library() override;
virtual bool is_library_open() const override;
virtual bool has_library_changed() const override;
virtual bool library_exists() const override;
Error parse_gdextension_file(const String &p_path);
};
#endif // GDEXTENSION_LIBRARY_LOADER_H

View file

@ -0,0 +1,48 @@
/**************************************************************************/
/* gdextension_loader.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDEXTENSION_LOADER_H
#define GDEXTENSION_LOADER_H
#include "core/object/ref_counted.h"
class GDExtension;
class GDExtensionLoader : public RefCounted {
public:
virtual Error open_library(const String &p_path) = 0;
virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) = 0;
virtual void close_library() = 0;
virtual bool is_library_open() const = 0;
virtual bool has_library_changed() const = 0;
virtual bool library_exists() const = 0;
};
#endif // GDEXTENSION_LOADER_H

View file

@ -30,15 +30,20 @@
#include "gdextension_manager.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_library_loader.h"
#include "core/extension/gdextension_special_compat_hashes.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/object/script_language.h"
GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) {
GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
if (level >= 0) { // Already initialized up to some level.
int32_t minimum_level = p_extension->get_minimum_library_initialization_level();
if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
return LOAD_STATUS_NEEDS_RESTART;
int32_t minimum_level = 0;
if (!p_first_load) {
minimum_level = p_extension->get_minimum_library_initialization_level();
if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
return LOAD_STATUS_NEEDS_RESTART;
}
}
// Initialize up to current level.
for (int32_t i = minimum_level; i <= level; i++) {
@ -50,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons
gdextension_class_icon_paths[kv.key] = kv.value;
}
#ifdef TOOLS_ENABLED
// Signals that a new extension is loaded so GDScript can register new class names.
emit_signal("extension_loaded", p_extension);
#endif
return LOAD_STATUS_OK;
}
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
#ifdef TOOLS_ENABLED
// Signals that a new extension is unloading so GDScript can unregister class names.
emit_signal("extension_unloading", p_extension);
#endif
if (level >= 0) { // Already initialized up to some level.
// Deinitialize down from current level.
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
@ -69,19 +84,35 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co
}
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
if (gdextension_map.has(p_path)) {
return LOAD_STATUS_ALREADY_LOADED;
}
Ref<GDExtension> extension = ResourceLoader::load(p_path);
if (extension.is_null()) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
LoadStatus status = _load_extension_internal(extension);
Ref<GDExtensionLibraryLoader> loader;
loader.instantiate();
return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader);
}
GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
DEV_ASSERT(p_loader.is_valid());
if (gdextension_map.has(p_path)) {
return LOAD_STATUS_ALREADY_LOADED;
}
Ref<GDExtension> extension;
extension.instantiate();
Error err = extension->open_library(p_path, p_loader);
if (err != OK) {
return LOAD_STATUS_FAILED;
}
LoadStatus status = _load_extension_internal(extension, true);
if (status != LOAD_STATUS_OK) {
return status;
}
extension->set_path(p_path);
gdextension_map[p_path] = extension;
return LOAD_STATUS_OK;
}
@ -92,6 +123,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
#else
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
@ -117,12 +152,12 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
extension->close_library();
}
Error err = GDExtensionResourceLoader::load_gdextension_resource(p_path, extension);
Error err = extension->open_library(p_path, extension->loader);
if (err != OK) {
return LOAD_STATUS_FAILED;
}
status = _load_extension_internal(extension);
status = _load_extension_internal(extension, false);
if (status != LOAD_STATUS_OK) {
return status;
}
@ -134,6 +169,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
}
GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
@ -180,14 +219,28 @@ String GDExtensionManager::class_get_icon_path(const String &p_class) const {
}
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
E.value->initialize_library(p_level);
if (p_level == GDExtension::INITIALIZATION_LEVEL_EDITOR) {
for (const KeyValue<String, String> &kv : E.value->class_icon_paths) {
gdextension_class_icon_paths[kv.key] = kv.value;
}
}
}
level = p_level;
}
void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
ERR_FAIL_COND(int32_t(p_level) != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
E.value->deinitialize_library(p_level);
@ -226,12 +279,16 @@ void GDExtensionManager::_reload_all_scripts() {
#endif // TOOLS_ENABLED
void GDExtensionManager::load_extensions() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
while (f.is_valid() && !f->eof_reached()) {
String s = f->get_line().strip_edges();
if (!s.is_empty()) {
LoadStatus err = load_extension(s);
ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s);
ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
}
}
@ -240,6 +297,9 @@ void GDExtensionManager::load_extensions() {
void GDExtensionManager::reload_extensions() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
bool reloaded = false;
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
if (!E.value->is_reloadable()) {
@ -261,6 +321,72 @@ void GDExtensionManager::reload_extensions() {
#endif
}
bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
Vector<String> extensions_added;
Vector<String> extensions_removed;
for (const String &E : p_extensions) {
if (!is_extension_loaded(E)) {
extensions_added.push_back(E);
}
}
Vector<String> loaded_extensions = get_loaded_extensions();
for (const String &loaded_extension : loaded_extensions) {
if (!p_extensions.has(loaded_extension)) {
// The extension may not have a .gdextension file.
const Ref<GDExtension> extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension);
if (!extension->get_loader()->library_exists()) {
extensions_removed.push_back(loaded_extension);
}
}
}
String extension_list_config_file = GDExtension::get_extension_list_config_file();
if (p_extensions.size()) {
if (extensions_added.size() || extensions_removed.size()) {
// Extensions were added or removed.
Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
for (const String &E : p_extensions) {
f->store_line(E);
}
}
} else {
if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
// Extensions were removed.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
da->remove(extension_list_config_file);
}
}
bool needs_restart = false;
for (const String &extension : extensions_added) {
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
needs_restart = true;
}
}
for (const String &extension : extensions_removed) {
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
needs_restart = true;
}
}
#ifdef TOOLS_ENABLED
if (extensions_added.size() || extensions_removed.size()) {
// Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
emit_signal("extensions_reloaded");
// Reload all scripts to clear out old references.
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
}
#endif
return needs_restart;
}
GDExtensionManager *GDExtensionManager::get_singleton() {
return singleton;
}
@ -281,6 +407,8 @@ void GDExtensionManager::_bind_methods() {
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
}
GDExtensionManager *GDExtensionManager::singleton = nullptr;
@ -290,7 +418,7 @@ GDExtensionManager::GDExtensionManager() {
singleton = this;
#ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes::initialize();
GDExtensionSpecialCompatHashes::initialize();
#endif
}
@ -299,6 +427,6 @@ GDExtensionManager::~GDExtensionManager() {
singleton = nullptr;
}
#ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes::finalize();
GDExtensionSpecialCompatHashes::finalize();
#endif
}

View file

@ -54,7 +54,7 @@ public:
};
private:
LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension);
LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
#ifdef TOOLS_ENABLED
@ -63,6 +63,7 @@ private:
public:
LoadStatus load_extension(const String &p_path);
LoadStatus load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader);
LoadStatus reload_extension(const String &p_path);
LoadStatus unload_extension(const String &p_path);
bool is_extension_loaded(const String &p_path) const;
@ -84,6 +85,7 @@ public:
void load_extensions();
void reload_extensions();
bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
GDExtensionManager();
~GDExtensionManager();

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* gdextension_compat_hashes.cpp */
/* gdextension_special_compat_hashes.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,16 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdextension_compat_hashes.h"
#include "gdextension_special_compat_hashes.h"
#ifndef DISABLE_DEPRECATED
#include "core/object/class_db.h"
#include "core/variant/variant.h"
HashMap<StringName, LocalVector<GDExtensionCompatHashes::Mapping>> GDExtensionCompatHashes::mappings;
HashMap<StringName, LocalVector<GDExtensionSpecialCompatHashes::Mapping>> GDExtensionSpecialCompatHashes::mappings;
bool GDExtensionCompatHashes::lookup_current_hash(const StringName &p_class, const StringName &p_method, uint32_t p_legacy_hash, uint32_t *r_current_hash) {
bool GDExtensionSpecialCompatHashes::lookup_current_hash(const StringName &p_class, const StringName &p_method, uint32_t p_legacy_hash, uint32_t *r_current_hash) {
LocalVector<Mapping> *methods = mappings.getptr(p_class);
if (!methods) {
return false;
@ -53,7 +53,7 @@ bool GDExtensionCompatHashes::lookup_current_hash(const StringName &p_class, con
return false;
}
bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid) {
bool GDExtensionSpecialCompatHashes::get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid) {
LocalVector<Mapping> *methods = mappings.getptr(p_class);
if (!methods) {
return false;
@ -65,7 +65,7 @@ bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const
if (p_check_valid) {
MethodBind *mb = ClassDB::get_method_with_compatibility(p_class, p_method, mapping.current_hash);
if (!mb) {
WARN_PRINT(vformat("Compatibility hash %d for %s::%s() mapped to non-existent hash %d. Please update gdextension_compat_hashes.cpp.", mapping.legacy_hash, p_class, p_method, mapping.current_hash));
WARN_PRINT(vformat("Compatibility hash %d for %s::%s() mapped to non-existent hash %d. Please update gdextension_special_compat_hashes.cpp.", mapping.legacy_hash, p_class, p_method, mapping.current_hash));
continue;
}
}
@ -77,7 +77,7 @@ bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const
return found;
}
void GDExtensionCompatHashes::initialize() {
void GDExtensionSpecialCompatHashes::initialize() {
// clang-format off
mappings.insert("AESContext", {
{ "start", 3167574919, 3122411423 },
@ -103,6 +103,14 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("AcceptDialog", {
{ "add_button", 4158837846, 3328440682 },
});
mappings.insert("AnimatedSprite2D", {
{ "play", 2372066587, 3269405555 },
{ "play_backwards", 1421762485, 3323268493 },
});
mappings.insert("AnimatedSprite3D", {
{ "play", 2372066587, 3269405555 },
{ "play_backwards", 1421762485, 3323268493 },
});
mappings.insert("Animation", {
{ "add_track", 2393815928, 3843682357 },
{ "track_insert_key", 1985425300, 808952278 },
@ -146,6 +154,12 @@ void GDExtensionCompatHashes::initialize() {
{ "travel", 3683006648, 3823612587 },
{ "start", 3683006648, 3823612587 },
});
mappings.insert("AnimationPlayer", {
{ "play", 3697947785, 3118260607 },
{ "play", 2221377757, 3118260607 },
{ "play_backwards", 3890664824, 2787282401 },
{ "play_with_capture", 3180464118, 1572969103 },
});
mappings.insert("ArrayMesh", {
{ "add_surface_from_arrays", 172284304, 1796411378 },
});
@ -241,22 +255,31 @@ void GDExtensionCompatHashes::initialize() {
#endif
});
mappings.insert("DirAccess", {
{ "list_dir_begin", 2018049411, 2610976713 },
{ "list_dir_begin", 2018049411, 166280745 },
{ "list_dir_begin", 2610976713, 166280745 },
{ "copy", 198434953, 1063198817 },
{ "copy_absolute", 198434953, 1063198817 },
});
mappings.insert("DisplayServer", {
{ "global_menu_add_submenu_item", 3806306913, 2828985934 },
{ "global_menu_add_item", 3415468211, 3401266716 },
{ "global_menu_add_check_item", 3415468211, 3401266716 },
{ "global_menu_add_icon_item", 1700867534, 4245856523 },
{ "global_menu_add_icon_check_item", 1700867534, 4245856523 },
{ "global_menu_add_radio_check_item", 3415468211, 3401266716 },
{ "global_menu_add_icon_radio_check_item", 1700867534, 4245856523 },
{ "global_menu_add_multistate_item", 635750054, 3431222859 },
{ "global_menu_add_item", 3415468211, 3616842746 },
{ "global_menu_add_item", 3401266716, 3616842746 },
{ "global_menu_add_check_item", 3415468211, 3616842746 },
{ "global_menu_add_check_item", 3401266716, 3616842746 },
{ "global_menu_add_icon_item", 1700867534, 3867083847 },
{ "global_menu_add_icon_item", 4245856523, 3867083847 },
{ "global_menu_add_icon_check_item", 1700867534, 3867083847 },
{ "global_menu_add_icon_check_item", 4245856523, 3867083847 },
{ "global_menu_add_radio_check_item", 3415468211, 3616842746 },
{ "global_menu_add_radio_check_item", 3401266716, 3616842746 },
{ "global_menu_add_icon_radio_check_item", 1700867534, 3867083847 },
{ "global_menu_add_icon_radio_check_item", 4245856523, 3867083847 },
{ "global_menu_add_multistate_item", 635750054, 3297554655 },
{ "global_menu_add_multistate_item", 3431222859, 3297554655 },
{ "global_menu_add_separator", 1041533178, 3214812433 },
{ "tts_speak", 3741216677, 903992738 },
{ "is_touchscreen_available", 4162880507, 3323674545 },
{ "is_touchscreen_available", 4162880507, 36873697 },
{ "is_touchscreen_available", 3323674545, 36873697 },
{ "screen_set_orientation", 2629526904, 2211511631 },
{ "window_get_native_handle", 2709193271, 1096425680 },
{ "window_set_title", 3043792800, 441246282 },
@ -286,6 +309,12 @@ void GDExtensionCompatHashes::initialize() {
{ "virtual_keyboard_show", 860410478, 3042891259 },
#endif
});
mappings.insert("EditorExportPlatform", {
{ "export_project_files", 425454869, 1063735070 },
});
mappings.insert("EditorProperty", {
{ "emit_changed", 3069422438, 1822500399 },
});
mappings.insert("ENetConnection", {
{ "create_host_bound", 866250949, 1515002313 },
{ "connect_to_host", 385984708, 2171300490 },
@ -453,18 +482,35 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("MultiplayerAPI", {
{ "rpc", 1833408346, 2077486355 },
});
mappings.insert("NativeMenu", {
{ "add_item", 2553375659, 980552939 },
{ "add_check_item", 2553375659, 980552939 },
{ "add_icon_item", 2987595282, 1372188274 },
{ "add_icon_check_item", 2987595282, 1372188274 },
{ "add_radio_check_item", 2553375659, 980552939 },
{ "add_icon_radio_check_item", 2987595282, 1372188274 },
{ "add_multistate_item", 1558592568, 2674635658 },
});
mappings.insert("NavigationMeshGenerator", {
{ "parse_source_geometry_data", 3703028813, 685862123 },
{ "bake_from_source_geometry_data", 3669016597, 2469318639 },
{ "parse_source_geometry_data", 3703028813, 3172802542 },
{ "parse_source_geometry_data", 685862123, 3172802542 },
{ "bake_from_source_geometry_data", 3669016597, 1286748856 },
{ "bake_from_source_geometry_data", 2469318639, 1286748856 },
});
mappings.insert("NavigationServer2D", {
{ "map_get_path", 56240621, 3146466012 },
{ "parse_source_geometry_data", 1176164995, 1766905497 },
{ "bake_from_source_geometry_data", 2909414286, 2179660022 },
{ "bake_from_source_geometry_data_async", 2909414286, 2179660022 },
});
mappings.insert("NavigationServer3D", {
{ "map_get_path", 2121045993, 1187418690 },
{ "parse_source_geometry_data", 3703028813, 685862123 },
{ "bake_from_source_geometry_data", 3669016597, 2469318639 },
{ "bake_from_source_geometry_data_async", 3669016597, 2469318639 },
{ "parse_source_geometry_data", 3703028813, 3172802542 },
{ "parse_source_geometry_data", 685862123, 3172802542 },
{ "bake_from_source_geometry_data", 3669016597, 1286748856 },
{ "bake_from_source_geometry_data", 2469318639, 1286748856 },
{ "bake_from_source_geometry_data_async", 3669016597, 1286748856 },
{ "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
});
mappings.insert("Node", {
{ "add_child", 3070154285, 3863233950 },
@ -504,6 +550,9 @@ void GDExtensionCompatHashes::initialize() {
{ "tr", 2475554935, 1195764410 },
{ "tr_n", 4021311862, 162698058 },
});
mappings.insert("OpenXRAPIExtension", {
{ "transform_from_pose", 3255299855, 2963875352 },
});
mappings.insert("OptionButton", {
{ "add_item", 3043792800, 2697778442 },
{ "add_icon_item", 3944051090, 3781678508 },
@ -631,6 +680,11 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("ProjectSettings", {
{ "load_resource_pack", 3001721055, 708980503 },
});
mappings.insert("RDShaderFile", {
{ "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
{ "set_bytecode", 1558064255, 1526857008 },
{ "get_spirv", 3340165340, 2689310080 },
});
mappings.insert("RegEx", {
{ "search", 4087180739, 3365977994 },
{ "search_all", 3354100289, 849021363 },
@ -722,7 +776,7 @@ void GDExtensionCompatHashes::initialize() {
{ "push_paragraph", 3218895358, 3089306873 },
{ "push_list", 4036303897, 3017143144 },
{ "push_table", 1125058220, 2623499273 },
{ "set_table_column_expand", 4132157579, 2185176273 },
{ "set_table_column_expand", 4258957458, 2185176273 },
#ifdef REAL_T_IS_DOUBLE
{ "add_image", 3346058748, 1507062345 },
{ "push_dropcap", 981432822, 763534173 },
@ -863,6 +917,9 @@ void GDExtensionCompatHashes::initialize() {
{ "set_cells_terrain_path", 3072115677, 3578627656 },
{ "get_used_cells_by_id", 4152068407, 2931012785 },
});
mappings.insert("TileMapLayer", {
{ "notify_runtime_tile_data_update", 2275361663, 3218959716 },
});
mappings.insert("TileMapPattern", {
{ "set_cell", 634000503, 2224802556 },
});
@ -964,7 +1021,7 @@ void GDExtensionCompatHashes::initialize() {
// clang-format on
}
void GDExtensionCompatHashes::finalize() {
void GDExtensionSpecialCompatHashes::finalize() {
mappings.clear();
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* gdextension_compat_hashes.h */
/* gdextension_special_compat_hashes.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDEXTENSION_COMPAT_HASHES_H
#define GDEXTENSION_COMPAT_HASHES_H
#ifndef GDEXTENSION_SPECIAL_COMPAT_HASHES_H
#define GDEXTENSION_SPECIAL_COMPAT_HASHES_H
#ifndef DISABLE_DEPRECATED
@ -37,7 +37,11 @@
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
class GDExtensionCompatHashes {
// Note: In most situations, compatibility methods should be registered via ClassDB::bind_compatibility_method().
// This class is only meant to be used in exceptional circumstances, for example, when Godot's hashing
// algorithm changes and registering compatibility methods for all affect methods would be onerous.
class GDExtensionSpecialCompatHashes {
struct Mapping {
StringName method;
uint32_t legacy_hash;
@ -55,4 +59,4 @@ public:
#endif // DISABLE_DEPRECATED
#endif // GDEXTENSION_COMPAT_HASHES_H
#endif // GDEXTENSION_SPECIAL_COMPAT_HASHES_H

View file

@ -55,10 +55,10 @@ def generate_mod_version(argcount, const=False, returns=False):
proto_ex = """
#define EXBIND$VER($RETTYPE m_name$ARG) \\
GDVIRTUAL$VER($RETTYPE_##m_name$ARG)\\
GDVIRTUAL$VER_REQUIRED($RETTYPE_##m_name$ARG)\\
virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
$RETPRE\\
GDVIRTUAL_REQUIRED_CALL(_##m_name$CALLARGS$RETREF);\\
GDVIRTUAL_CALL(_##m_name$CALLARGS$RETREF);\\
$RETPOST\\
}
"""

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back
Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android,
# Web
standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web,
Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,
Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web,

View file

@ -77,6 +77,10 @@ Input *Input::singleton = nullptr;
void (*Input::set_mouse_mode_func)(Input::MouseMode) = nullptr;
Input::MouseMode (*Input::get_mouse_mode_func)() = nullptr;
void (*Input::set_mouse_mode_override_func)(Input::MouseMode) = nullptr;
Input::MouseMode (*Input::get_mouse_mode_override_func)() = nullptr;
void (*Input::set_mouse_mode_override_enabled_func)(bool) = nullptr;
bool (*Input::is_mouse_mode_override_enabled_func)() = nullptr;
void (*Input::warp_mouse_func)(const Vector2 &p_position) = nullptr;
Input::CursorShape (*Input::get_current_cursor_shape_func)() = nullptr;
void (*Input::set_custom_mouse_cursor_func)(const Ref<Resource> &, Input::CursorShape, const Vector2 &) = nullptr;
@ -86,7 +90,7 @@ Input *Input::get_singleton() {
}
void Input::set_mouse_mode(MouseMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 5);
ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);
set_mouse_mode_func(p_mode);
}
@ -94,6 +98,23 @@ Input::MouseMode Input::get_mouse_mode() const {
return get_mouse_mode_func();
}
void Input::set_mouse_mode_override(MouseMode p_mode) {
ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);
set_mouse_mode_override_func(p_mode);
}
Input::MouseMode Input::get_mouse_mode_override() const {
return get_mouse_mode_override_func();
}
void Input::set_mouse_mode_override_enabled(bool p_override_enabled) {
set_mouse_mode_override_enabled_func(p_override_enabled);
}
bool Input::is_mouse_mode_override_enabled() {
return is_mouse_mode_override_enabled_func();
}
void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_anything_pressed"), &Input::is_anything_pressed);
ClassDB::bind_method(D_METHOD("is_key_pressed", "keycode"), &Input::is_key_pressed);
@ -160,6 +181,7 @@ void Input::_bind_methods() {
BIND_ENUM_CONSTANT(MOUSE_MODE_CAPTURED);
BIND_ENUM_CONSTANT(MOUSE_MODE_CONFINED);
BIND_ENUM_CONSTANT(MOUSE_MODE_CONFINED_HIDDEN);
BIND_ENUM_CONSTANT(MOUSE_MODE_MAX);
BIND_ENUM_CONSTANT(CURSOR_ARROW);
BIND_ENUM_CONSTANT(CURSOR_IBEAM);
@ -197,7 +219,7 @@ void Input::get_argument_options(const StringName &p_function, int p_idx, List<S
continue;
}
String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length());
String name = pi.name.substr(pi.name.find_char('/') + 1, pi.name.length());
r_options->push_back(name.quote());
}
}
@ -252,6 +274,10 @@ Input::VelocityTrack::VelocityTrack() {
bool Input::is_anything_pressed() const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty() || !mouse_button_mask.is_empty()) {
return true;
}
@ -265,23 +291,63 @@ bool Input::is_anything_pressed() const {
return false;
}
bool Input::is_anything_pressed_except_mouse() const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty()) {
return true;
}
for (const KeyValue<StringName, Input::ActionState> &E : action_states) {
if (E.value.cache.pressed) {
return true;
}
}
return false;
}
bool Input::is_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
return keys_pressed.has(p_keycode);
}
bool Input::is_physical_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
return physical_keys_pressed.has(p_keycode);
}
bool Input::is_key_label_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
return key_label_pressed.has(p_keycode);
}
bool Input::is_mouse_button_pressed(MouseButton p_button) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
return mouse_button_mask.has_flag(mouse_button_to_mask(p_button));
}
@ -295,11 +361,21 @@ static JoyButton _combine_device(JoyButton p_value, int p_device) {
bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return false;
}
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
if (disable_input) {
return false;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@ -310,6 +386,11 @@ bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
if (disable_input) {
return false;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@ -331,6 +412,11 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
if (disable_input) {
return false;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@ -352,6 +438,11 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
if (disable_input) {
return 0.0f;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@ -366,6 +457,11 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
if (disable_input) {
return 0.0f;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@ -410,6 +506,11 @@ Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_po
float Input::get_joy_axis(int p_device, JoyAxis p_axis) const {
_THREAD_SAFE_METHOD_
if (disable_input) {
return 0;
}
JoyAxis c = _combine_device(p_axis, p_device);
if (_joy_axis.has(c)) {
return _joy_axis[c];
@ -491,10 +592,9 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
for (int i = 0; i < map_db.size(); i++) {
if (js.uid == map_db[i].uid) {
mapping = i;
js.name = map_db[i].name;
}
}
js.mapping = mapping;
_set_joypad_mapping(js, mapping);
} else {
js.connected = false;
for (int i = 0; i < (int)JoyButton::MAX; i++) {
@ -513,21 +613,49 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
Vector3 Input::get_gravity() const {
_THREAD_SAFE_METHOD_
#ifdef DEBUG_ENABLED
if (!gravity_enabled) {
WARN_PRINT_ONCE("`input_devices/sensors/enable_gravity` is not enabled in project settings.");
}
#endif
return gravity;
}
Vector3 Input::get_accelerometer() const {
_THREAD_SAFE_METHOD_
#ifdef DEBUG_ENABLED
if (!accelerometer_enabled) {
WARN_PRINT_ONCE("`input_devices/sensors/enable_accelerometer` is not enabled in project settings.");
}
#endif
return accelerometer;
}
Vector3 Input::get_magnetometer() const {
_THREAD_SAFE_METHOD_
#ifdef DEBUG_ENABLED
if (!magnetometer_enabled) {
WARN_PRINT_ONCE("`input_devices/sensors/enable_magnetometer` is not enabled in project settings.");
}
#endif
return magnetometer;
}
Vector3 Input::get_gyroscope() const {
_THREAD_SAFE_METHOD_
#ifdef DEBUG_ENABLED
if (!gyroscope_enabled) {
WARN_PRINT_ONCE("`input_devices/sensors/enable_gyroscope` is not enabled in project settings.");
}
#endif
return gyroscope;
}
@ -662,6 +790,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
button_event->set_canceled(st->is_canceled());
button_event->set_button_index(MouseButton::LEFT);
button_event->set_double_click(st->is_double_tap());
button_event->set_window_id(st->get_window_id());
BitField<MouseButtonMask> ev_bm = mouse_button_mask;
if (st->is_pressed()) {
@ -699,6 +828,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
motion_event->set_velocity(sd->get_velocity());
motion_event->set_screen_velocity(sd->get_screen_velocity());
motion_event->set_button_mask(mouse_button_mask);
motion_event->set_window_id(sd->get_window_id());
_parse_input_event_impl(motion_event, true);
}
@ -906,7 +1036,7 @@ void Input::action_release(const StringName &p_action) {
// Create or retrieve existing action.
ActionState &action_state = action_states[p_action];
action_state.cache.pressed = 0;
action_state.cache.pressed = false;
action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0;
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
@ -1455,9 +1585,6 @@ void Input::parse_mapping(const String &p_mapping) {
return;
}
CharString uid;
uid.resize(17);
mapping.uid = entry[0];
mapping.name = entry[1];
@ -1558,26 +1685,94 @@ void Input::add_joy_mapping(const String &p_mapping, bool p_update_existing) {
for (KeyValue<int, Joypad> &E : joy_names) {
Joypad &joy = E.value;
if (joy.uid == uid) {
joy.mapping = map_db.size() - 1;
_set_joypad_mapping(joy, map_db.size() - 1);
}
}
}
}
void Input::remove_joy_mapping(const String &p_guid) {
// One GUID can exist multiple times in `map_db`, and
// `add_joy_mapping` can choose not to update the existing mapping,
// so the indices can be all over the place. Therefore we need to remember them.
Vector<int> removed_idx;
int min_removed_idx = -1;
int max_removed_idx = -1;
int fallback_mapping_offset = 0;
for (int i = map_db.size() - 1; i >= 0; i--) {
if (p_guid == map_db[i].uid) {
map_db.remove_at(i);
if (max_removed_idx == -1) {
max_removed_idx = i;
}
min_removed_idx = i;
removed_idx.push_back(i);
if (i < fallback_mapping) {
fallback_mapping_offset++;
} else if (i == fallback_mapping) {
fallback_mapping = -1;
WARN_PRINT_ONCE(vformat("Removed fallback joypad input mapping \"%s\". This could lead to joypads not working as intended.", p_guid));
}
}
}
if (min_removed_idx == -1) {
return; // Nothing removed.
}
if (fallback_mapping > 0) {
// Fix the shifted index.
fallback_mapping -= fallback_mapping_offset;
}
int removed_idx_size = removed_idx.size();
// Update joypad mapping references: some
// * should use the fallback_mapping (if set; if not, they get unmapped), or
// * need their mapping reference fixed, because the deletion(s) offset them.
for (KeyValue<int, Joypad> &E : joy_names) {
Joypad &joy = E.value;
if (joy.uid == p_guid) {
joy.mapping = -1;
if (joy.mapping < min_removed_idx) {
continue; // Not affected.
}
if (joy.mapping > max_removed_idx) {
_set_joypad_mapping(joy, joy.mapping - removed_idx_size);
continue; // Simple offset fix.
}
// removed_idx is in reverse order (ie. high to low), because the first loop is in reverse order.
for (int i = 0; i < removed_idx.size(); i++) {
if (removed_idx[i] == joy.mapping) {
// Set to fallback_mapping, if defined, else unmap the joypad.
// Currently, the fallback_mapping is only set internally, and only for Android.
_set_joypad_mapping(joy, fallback_mapping);
break;
}
if (removed_idx[i] < joy.mapping) {
// Complex offset fix:
// This mapping was shifted by `(removed_idx_size - i)` deletions.
_set_joypad_mapping(joy, joy.mapping - (removed_idx_size - i));
break;
}
}
}
}
void Input::_set_joypad_mapping(Joypad &p_js, int p_map_index) {
if (p_map_index != fallback_mapping && p_map_index >= 0 && p_map_index < map_db.size() && p_js.uid != "__XINPUT_DEVICE__") {
// Prefer the joypad name defined in the mapping.
// Exceptions:
// * On Windows for XInput devices the mapping would change the joypad's name to a collective name.
// * A fallback mapping is not allowed to override the joypad's name.
p_js.name = map_db[p_map_index].name;
}
p_js.mapping = p_map_index;
}
void Input::set_fallback_mapping(const String &p_guid) {
for (int i = 0; i < map_db.size(); i++) {
if (map_db[i].uid == p_guid) {
@ -1634,6 +1829,14 @@ int Input::get_unused_joy_id() {
return -1;
}
void Input::set_disable_input(bool p_disable) {
disable_input = p_disable;
}
bool Input::is_input_disabled() const {
return disable_input;
}
Input::Input() {
singleton = this;
@ -1683,6 +1886,11 @@ Input::Input() {
// Always use standard behavior in the editor.
legacy_just_pressed_behavior = false;
}
accelerometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_accelerometer", false);
gravity_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gravity", false);
gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false);
magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false);
}
Input::~Input() {

View file

@ -47,12 +47,14 @@ class Input : public Object {
static constexpr uint64_t MAX_EVENT = 32;
public:
// Keep synced with "DisplayServer::MouseMode" enum.
enum MouseMode {
MOUSE_MODE_VISIBLE,
MOUSE_MODE_HIDDEN,
MOUSE_MODE_CAPTURED,
MOUSE_MODE_CONFINED,
MOUSE_MODE_CONFINED_HIDDEN,
MOUSE_MODE_MAX,
};
#undef CursorShape
@ -92,13 +94,18 @@ private:
RBSet<JoyButton> joy_buttons_pressed;
RBMap<JoyAxis, float> _joy_axis;
//RBMap<StringName,int> custom_action_press;
bool gravity_enabled = false;
Vector3 gravity;
bool accelerometer_enabled = false;
Vector3 accelerometer;
bool magnetometer_enabled = false;
Vector3 magnetometer;
bool gyroscope_enabled = false;
Vector3 gyroscope;
Vector2 mouse_pos;
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;
bool disable_input = false;
struct ActionState {
uint64_t pressed_physics_frame = UINT64_MAX;
@ -175,7 +182,7 @@ private:
HashSet<uint32_t> ignored_device_ids;
int fallback_mapping = -1;
int fallback_mapping = -1; // Index of the guid in map_db.
CursorShape default_shape = CURSOR_ARROW;
@ -236,6 +243,8 @@ private:
Vector<JoyDeviceMapping> map_db;
void _set_joypad_mapping(Joypad &p_js, int p_map_index);
JoyEvent _get_mapped_button_event(const JoyDeviceMapping &mapping, JoyButton p_button);
JoyEvent _get_mapped_axis_event(const JoyDeviceMapping &mapping, JoyAxis p_axis, float p_value, JoyAxisRange &r_range);
void _get_mapped_hat_events(const JoyDeviceMapping &mapping, HatDir p_hat, JoyEvent r_events[(size_t)HatDir::MAX]);
@ -257,6 +266,10 @@ private:
static void (*set_mouse_mode_func)(MouseMode);
static MouseMode (*get_mouse_mode_func)();
static void (*set_mouse_mode_override_func)(MouseMode);
static MouseMode (*get_mouse_mode_override_func)();
static void (*set_mouse_mode_override_enabled_func)(bool);
static bool (*is_mouse_mode_override_enabled_func)();
static void (*warp_mouse_func)(const Vector2 &p_position);
static CursorShape (*get_current_cursor_shape_func)();
@ -275,6 +288,10 @@ protected:
public:
void set_mouse_mode(MouseMode p_mode);
MouseMode get_mouse_mode() const;
void set_mouse_mode_override(MouseMode p_mode);
MouseMode get_mouse_mode_override() const;
void set_mouse_mode_override_enabled(bool p_override_enabled);
bool is_mouse_mode_override_enabled();
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
@ -283,6 +300,7 @@ public:
static Input *get_singleton();
bool is_anything_pressed() const;
bool is_anything_pressed_except_mouse() const;
bool is_key_pressed(Key p_keycode) const;
bool is_physical_key_pressed(Key p_keycode) const;
bool is_key_label_pressed(Key p_keycode) const;
@ -376,6 +394,9 @@ public:
void set_event_dispatch_function(EventDispatchFunc p_function);
void set_disable_input(bool p_disable);
bool is_input_disabled() const;
Input();
~Input();
};

View file

@ -33,7 +33,7 @@ def make_default_controller_mappings(target, source, env):
guid = line_parts[0]
if guid in platform_mappings[current_platform]:
g.write(
"// WARNING - DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format(
"// WARNING: DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format(
src_path, current_platform, platform_mappings[current_platform][guid]
)
)

View file

@ -31,6 +31,8 @@
#ifndef INPUT_ENUMS_H
#define INPUT_ENUMS_H
#include "core/error/error_macros.h"
enum class HatDir {
UP = 0,
RIGHT = 1,
@ -131,6 +133,8 @@ enum class MouseButtonMask {
};
inline MouseButtonMask mouse_button_to_mask(MouseButton button) {
ERR_FAIL_COND_V(button == MouseButton::NONE, MouseButtonMask::NONE);
return MouseButtonMask(1 << ((int)button - 1));
}

View file

@ -754,6 +754,8 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
mb->set_factor(factor);
mb->set_button_index(button_index);
mb->merge_meta_from(this);
return mb;
}
@ -974,6 +976,8 @@ Ref<InputEvent> InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co
mm->set_velocity(p_xform.basis_xform(get_velocity()));
mm->set_screen_velocity(get_screen_velocity());
mm->merge_meta_from(this);
return mm;
}
@ -1088,7 +1092,7 @@ void InputEventMouseMotion::_bind_methods() {
///////////////////////////////////
void InputEventJoypadMotion::set_axis(JoyAxis p_axis) {
ERR_FAIL_COND(p_axis < JoyAxis::LEFT_X || p_axis > JoyAxis::MAX);
ERR_FAIL_COND(p_axis < JoyAxis::INVALID || p_axis > JoyAxis::MAX);
axis = p_axis;
emit_changed();
@ -1100,7 +1104,7 @@ JoyAxis InputEventJoypadMotion::get_axis() const {
void InputEventJoypadMotion::set_axis_value(float p_value) {
axis_value = p_value;
pressed = Math::abs(axis_value) >= 0.5f;
pressed = Math::abs(axis_value) >= InputMap::DEFAULT_DEADZONE;
emit_changed();
}
@ -1366,6 +1370,8 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co
st->set_canceled(canceled);
st->set_double_tap(double_tap);
st->merge_meta_from(this);
return st;
}
@ -1494,6 +1500,8 @@ Ref<InputEvent> InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con
sd->set_velocity(p_xform.basis_xform(velocity));
sd->set_screen_velocity(get_screen_velocity());
sd->merge_meta_from(this);
return sd;
}
@ -1705,6 +1713,8 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform,
ev->set_position(p_xform.xform(get_position() + p_local_ofs));
ev->set_factor(get_factor());
ev->merge_meta_from(this);
return ev;
}
@ -1745,6 +1755,8 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con
ev->set_position(p_xform.xform(get_position() + p_local_ofs));
ev->set_delta(get_delta());
ev->merge_meta_from(this);
return ev;
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* object.compat.inc */
/* input_map.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,11 +30,12 @@
#ifndef DISABLE_DEPRECATED
#include "core/object/class_db.h"
void Object::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
void InputMap::_add_action_bind_compat_97281(const StringName &p_action, float p_deadzone) {
add_action(p_action, p_deadzone);
}
#endif
void InputMap::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::_add_action_bind_compat_97281, DEFVAL(0.5f));
}
#endif // DISABLE_DEPRECATED

View file

@ -29,6 +29,7 @@
/**************************************************************************/
#include "input_map.h"
#include "input_map.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/input.h"
@ -43,7 +44,7 @@ int InputMap::ALL_DEVICES = -1;
void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action);
ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions);
ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f));
ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(DEFAULT_DEADZONE));
ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);
ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone);
@ -105,7 +106,7 @@ void InputMap::get_argument_options(const StringName &p_function, int p_idx, Lis
continue;
}
String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length());
String name = pi.name.substr(pi.name.find_char('/') + 1, pi.name.length());
r_options->push_back(name.quote());
}
}
@ -115,7 +116,7 @@ void InputMap::get_argument_options(const StringName &p_function, int p_idx, Lis
#endif
void InputMap::add_action(const StringName &p_action, float p_deadzone) {
ERR_FAIL_COND_MSG(input_map.has(p_action), "InputMap already has action \"" + String(p_action) + "\".");
ERR_FAIL_COND_MSG(input_map.has(p_action), vformat("InputMap already has action \"%s\".", String(p_action)));
input_map[p_action] = Action();
static int last_id = 1;
input_map[p_action].id = last_id;
@ -157,7 +158,7 @@ List<StringName> InputMap::get_actions() const {
}
List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const {
ERR_FAIL_COND_V(!p_event.is_valid(), nullptr);
ERR_FAIL_COND_V(p_event.is_null(), nullptr);
int i = 0;
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
@ -253,8 +254,8 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName
int InputMap::event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match) const {
int index = -1;
event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index);
return index;
bool valid = event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index);
return valid ? index : -1;
}
bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const {
@ -303,10 +304,10 @@ void InputMap::load_from_project_settings() {
continue;
}
String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length());
String name = pi.name.substr(pi.name.find_char('/') + 1, pi.name.length());
Dictionary action = GLOBAL_GET(pi.name);
float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f;
float deadzone = action.has("deadzone") ? (float)action["deadzone"] : DEFAULT_DEADZONE;
Array events = action["events"];
add_action(name, deadzone);
@ -400,6 +401,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
{ "ui_filedialog_refresh", TTRC("Refresh") },
{ "ui_filedialog_show_hidden", TTRC("Show Hidden") },
{ "ui_swap_input_direction ", TTRC("Swap Input Direction") },
{ "ui_unicode_start", TTRC("Start Unicode Character Input") },
{ "", ""}
/* clang-format on */
};
@ -517,12 +519,15 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
default_builtin_cache.insert("ui_text_completion_query", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::TAB));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::ENTER));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::KP_ENTER));
default_builtin_cache.insert("ui_text_completion_accept", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::TAB));
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
default_builtin_cache.insert("ui_text_completion_replace", inputs);
// Newlines
@ -532,7 +537,6 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
default_builtin_cache.insert("ui_text_newline", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::ENTER | KeyModifierMask::CMD_OR_CTRL));
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_text_newline_blank", inputs);
@ -754,6 +758,10 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
default_builtin_cache.insert("ui_text_submit", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::U | KeyModifierMask::CTRL | KeyModifierMask::SHIFT));
default_builtin_cache.insert("ui_unicode_start", inputs);
// ///// UI Graph Shortcuts /////
inputs = List<Ref<InputEvent>>();

View file

@ -54,6 +54,8 @@ public:
List<Ref<InputEvent>> inputs;
};
static constexpr float DEFAULT_DEADZONE = 0.2f;
private:
static InputMap *singleton;
@ -69,12 +71,17 @@ private:
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _add_action_bind_compat_97281(const StringName &p_action, float p_deadzone = 0.5);
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
static _FORCE_INLINE_ InputMap *get_singleton() { return singleton; }
bool has_action(const StringName &p_action) const;
List<StringName> get_actions() const;
void add_action(const StringName &p_action, float p_deadzone = 0.5);
void add_action(const StringName &p_action, float p_deadzone = DEFAULT_DEADZONE);
void erase_action(const StringName &p_action);
float action_get_deadzone(const StringName &p_action);

View file

@ -29,7 +29,6 @@
/**************************************************************************/
#include "shortcut.h"
#include "core/os/keyboard.h"
void Shortcut::set_events(const Array &p_events) {
for (int i = 0; i < p_events.size(); i++) {

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -43,7 +43,7 @@ public:
static int zstd_window_log_size;
static int gzip_chunk;
enum Mode {
enum Mode : int32_t {
MODE_FASTLZ,
MODE_DEFLATE,
MODE_ZSTD,

View file

@ -31,7 +31,6 @@
#include "config_file.h"
#include "core/io/file_access_encrypted.h"
#include "core/os/keyboard.h"
#include "core/string/string_builder.h"
#include "core/variant/variant_parser.h"

View file

@ -32,8 +32,8 @@
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/os/memory.h"
#include "core/os/os.h"
#include "core/os/time.h"
#include "core/templates/local_vector.h"
thread_local Error DirAccess::last_dir_open_error = OK;
@ -155,9 +155,9 @@ Error DirAccess::make_dir_recursive(const String &p_dir) {
} else if (full_dir.begins_with("user://")) {
base = "user://";
} else if (full_dir.is_network_share_path()) {
int pos = full_dir.find("/", 2);
int pos = full_dir.find_char('/', 2);
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
pos = full_dir.find("/", pos + 1);
pos = full_dir.find_char('/', pos + 1);
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
base = full_dir.substr(0, pos + 1);
} else if (full_dir.begins_with("/")) {
@ -177,7 +177,7 @@ Error DirAccess::make_dir_recursive(const String &p_dir) {
curpath = curpath.path_join(subdirs[i]);
Error err = make_dir(curpath);
if (err != OK && err != ERR_ALREADY_EXISTS) {
ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
ERR_FAIL_V_MSG(err, vformat("Could not create directory: '%s'.", curpath));
}
}
@ -239,7 +239,7 @@ Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
Ref<DirAccess> DirAccess::open(const String &p_path, Error *r_error) {
Ref<DirAccess> da = create_for_path(p_path);
ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, "Cannot create DirAccess for path '" + p_path + "'.");
ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, vformat("Cannot create DirAccess for path '%s'.", p_path));
Error err = da->change_dir(p_path);
if (r_error) {
*r_error = err;
@ -323,6 +323,80 @@ Ref<DirAccess> DirAccess::create(AccessType p_access) {
return da;
}
Ref<DirAccess> DirAccess::create_temp(const String &p_prefix, bool p_keep, Error *r_error) {
const String ERROR_COMMON_PREFIX = "Error while creating temporary directory";
if (!p_prefix.is_valid_filename()) {
*r_error = ERR_FILE_BAD_PATH;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix));
}
Ref<DirAccess> dir_access = DirAccess::open(OS::get_singleton()->get_temp_path());
uint32_t suffix_i = 0;
String path;
while (true) {
String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", "");
datetime += itos(Time::get_singleton()->get_ticks_usec());
String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : "");
path = (p_prefix.is_empty() ? "" : p_prefix + "-") + suffix;
if (!path.is_valid_filename()) {
*r_error = ERR_FILE_BAD_PATH;
return Ref<DirAccess>();
}
if (!DirAccess::exists(path)) {
break;
}
suffix_i += 1;
}
Error err = dir_access->make_dir(path);
if (err != OK) {
*r_error = err;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" couldn't create directory "%s".)", ERROR_COMMON_PREFIX, path));
}
err = dir_access->change_dir(path);
if (err != OK) {
*r_error = err;
return Ref<DirAccess>();
}
dir_access->_is_temp = true;
dir_access->_temp_keep_after_free = p_keep;
dir_access->_temp_path = dir_access->get_current_dir();
*r_error = OK;
return dir_access;
}
Ref<DirAccess> DirAccess::_create_temp(const String &p_prefix, bool p_keep) {
return create_temp(p_prefix, p_keep, &last_dir_open_error);
}
void DirAccess::_delete_temp() {
if (!_is_temp || _temp_keep_after_free) {
return;
}
if (!DirAccess::exists(_temp_path)) {
return;
}
Error err;
{
Ref<DirAccess> dir_access = DirAccess::open(_temp_path, &err);
if (err != OK) {
return;
}
err = dir_access->erase_contents_recursive();
if (err != OK) {
return;
}
}
DirAccess::remove_absolute(_temp_path);
}
Error DirAccess::get_open_error() {
return last_dir_open_error;
}
@ -345,10 +419,10 @@ Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flag
Error err;
{
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_from);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_from));
Ref<FileAccess> fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_to);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_to));
const size_t copy_buffer_limit = 65536; // 64 KB
@ -444,11 +518,11 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int
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 + "'.");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", target_dir));
}
Error err = change_dir(rel_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'.");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot change current directory to '%s'.", rel_path));
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
if (err) {
@ -466,11 +540,11 @@ Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags,
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
Ref<DirAccess> target_da = DirAccess::create_for_path(p_to);
ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_to));
if (!target_da->dir_exists(p_to)) {
Error err = target_da->make_dir_recursive(p_to);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", p_to));
}
if (!p_to.ends_with("/")) {
@ -555,8 +629,9 @@ bool DirAccess::is_case_sensitive(const String &p_path) const {
void DirAccess::_bind_methods() {
ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open);
ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error);
ClassDB::bind_static_method("DirAccess", D_METHOD("create_temp", "prefix", "keep"), &DirAccess::_create_temp, DEFVAL(""), DEFVAL(false));
ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin, DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin);
ClassDB::bind_method(D_METHOD("get_next"), &DirAccess::_get_next);
ClassDB::bind_method(D_METHOD("current_is_dir"), &DirAccess::current_is_dir);
ClassDB::bind_method(D_METHOD("list_dir_end"), &DirAccess::list_dir_end);
@ -588,6 +663,8 @@ void DirAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("read_link", "path"), &DirAccess::read_link);
ClassDB::bind_method(D_METHOD("create_link", "source", "target"), &DirAccess::create_link);
ClassDB::bind_method(D_METHOD("is_bundle", "path"), &DirAccess::is_bundle);
ClassDB::bind_method(D_METHOD("set_include_navigational", "enable"), &DirAccess::set_include_navigational);
ClassDB::bind_method(D_METHOD("get_include_navigational"), &DirAccess::get_include_navigational);
ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden);
@ -598,3 +675,7 @@ void DirAccess::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden");
}
DirAccess::~DirAccess() {
_delete_temp();
}

View file

@ -40,7 +40,7 @@ class DirAccess : public RefCounted {
GDCLASS(DirAccess, RefCounted);
public:
enum AccessType {
enum AccessType : int32_t {
ACCESS_RESOURCES,
ACCESS_USERDATA,
ACCESS_FILESYSTEM,
@ -61,6 +61,13 @@ private:
bool include_navigational = false;
bool include_hidden = false;
bool _is_temp = false;
bool _temp_keep_after_free = false;
String _temp_path;
void _delete_temp();
static Ref<DirAccess> _create_temp(const String &p_prefix = "", bool p_keep = false);
protected:
static void _bind_methods();
@ -96,8 +103,8 @@ public:
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; };
virtual bool is_readable(String p_dir) { return true; }
virtual bool is_writable(String p_dir) { return true; }
static bool exists(const String &p_dir);
virtual uint64_t get_space_left() = 0;
@ -116,10 +123,10 @@ public:
Ref<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);
ERR_FAIL_MSG(vformat("Cannot remove file or directory: '%s'.", p_path));
}
} else {
ERR_FAIL_MSG("Cannot remove non-existent file or directory: " + p_path);
ERR_FAIL_MSG(vformat("Cannot remove non-existent file or directory: '%s'.", p_path));
}
}
@ -136,6 +143,7 @@ public:
}
static Ref<DirAccess> open(const String &p_path, Error *r_error = nullptr);
static Ref<DirAccess> create_temp(const String &p_prefix = "", bool p_keep = false, Error *r_error = nullptr);
static int _get_drive_count();
static String get_drive_name(int p_idx);
@ -160,9 +168,11 @@ public:
bool get_include_hidden() const;
virtual bool is_case_sensitive(const String &p_path) const;
virtual bool is_bundle(const String &p_file) const { return false; }
public:
DirAccess() {}
virtual ~DirAccess() {}
virtual ~DirAccess();
};
#endif // DIR_ACCESS_H

View file

@ -30,15 +30,12 @@
#include "dtls_server.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
DTLSServer *(*DTLSServer::_create)() = nullptr;
DTLSServer *(*DTLSServer::_create)(bool p_notify_postinitialize) = nullptr;
bool DTLSServer::available = false;
DTLSServer *DTLSServer::create() {
DTLSServer *DTLSServer::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
return nullptr;
}

View file

@ -38,14 +38,14 @@ class DTLSServer : public RefCounted {
GDCLASS(DTLSServer, RefCounted);
protected:
static DTLSServer *(*_create)();
static DTLSServer *(*_create)(bool p_notify_postinitialize);
static void _bind_methods();
static bool available;
public:
static bool is_available();
static DTLSServer *create();
static DTLSServer *create(bool p_notify_postinitialize = true);
virtual Error setup(Ref<TLSOptions> p_options) = 0;
virtual void stop() = 0;

View file

@ -0,0 +1,112 @@
/**************************************************************************/
/* file_access.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
Ref<FileAccess> FileAccess::_open_encrypted_bind_compat_98918(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) {
return open_encrypted(p_path, p_mode_flags, p_key, Vector<uint8_t>());
}
void FileAccess::store_8_bind_compat_78289(uint8_t p_dest) {
store_8(p_dest);
}
void FileAccess::store_16_bind_compat_78289(uint16_t p_dest) {
store_16(p_dest);
}
void FileAccess::store_32_bind_compat_78289(uint32_t p_dest) {
store_32(p_dest);
}
void FileAccess::store_64_bind_compat_78289(uint64_t p_dest) {
store_64(p_dest);
}
void FileAccess::store_buffer_bind_compat_78289(const Vector<uint8_t> &p_buffer) {
store_buffer(p_buffer);
}
void FileAccess::store_var_bind_compat_78289(const Variant &p_var, bool p_full_objects) {
store_var(p_var, p_full_objects);
}
void FileAccess::store_half_bind_compat_78289(float p_dest) {
store_half(p_dest);
}
void FileAccess::store_float_bind_compat_78289(float p_dest) {
store_float(p_dest);
}
void FileAccess::store_double_bind_compat_78289(double p_dest) {
store_double(p_dest);
}
void FileAccess::store_real_bind_compat_78289(real_t p_real) {
store_real(p_real);
}
void FileAccess::store_string_bind_compat_78289(const String &p_string) {
store_string(p_string);
}
void FileAccess::store_line_bind_compat_78289(const String &p_line) {
store_line(p_line);
}
void FileAccess::store_csv_line_bind_compat_78289(const Vector<String> &p_values, const String &p_delim) {
store_csv_line(p_values, p_delim);
}
void FileAccess::store_pascal_string_bind_compat_78289(const String &p_string) {
store_pascal_string(p_string);
}
void FileAccess::_bind_compatibility_methods() {
ClassDB::bind_compatibility_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key"), &FileAccess::_open_encrypted_bind_compat_98918);
ClassDB::bind_compatibility_method(D_METHOD("store_8", "value"), &FileAccess::store_8_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_16", "value"), &FileAccess::store_16_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_32", "value"), &FileAccess::store_32_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_64", "value"), &FileAccess::store_64_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_half", "value"), &FileAccess::store_half_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_float", "value"), &FileAccess::store_float_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_double", "value"), &FileAccess::store_double_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_real", "value"), &FileAccess::store_real_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_buffer", "buffer"), &FileAccess::store_buffer_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_line", "line"), &FileAccess::store_line_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_csv_line", "values", "delim"), &FileAccess::store_csv_line_bind_compat_78289, DEFVAL(","));
ClassDB::bind_compatibility_method(D_METHOD("store_string", "string"), &FileAccess::store_string_bind_compat_78289);
ClassDB::bind_compatibility_method(D_METHOD("store_var", "value", "full_objects"), &FileAccess::store_var_bind_compat_78289, DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("store_pascal_string", "string"), &FileAccess::store_pascal_string_bind_compat_78289);
}
#endif

View file

@ -29,6 +29,7 @@
/**************************************************************************/
#include "file_access.h"
#include "file_access.compat.inc"
#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
@ -37,6 +38,7 @@
#include "core/io/file_access_pack.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
#include "core/os/time.h"
FileAccess::CreateFunc FileAccess::create_func[ACCESS_MAX] = {};
@ -59,11 +61,9 @@ bool FileAccess::exists(const String &p_name) {
return true;
}
Ref<FileAccess> f = open(p_name, READ);
if (f.is_null()) {
return false;
}
return true;
// Using file_exists because it's faster than trying to open the file.
Ref<FileAccess> ret = create_for_path(p_name);
return ret->file_exists(p_name);
}
void FileAccess::_set_access_type(AccessType p_access) {
@ -72,7 +72,7 @@ void FileAccess::_set_access_type(AccessType p_access) {
Ref<FileAccess> FileAccess::create_for_path(const String &p_path) {
Ref<FileAccess> ret;
if (p_path.begins_with("res://")) {
if (p_path.begins_with("res://") || p_path.begins_with("uid://")) {
ret = create(ACCESS_RESOURCES);
} else if (p_path.begins_with("user://")) {
ret = create(ACCESS_USERDATA);
@ -85,6 +85,79 @@ Ref<FileAccess> FileAccess::create_for_path(const String &p_path) {
return ret;
}
Ref<FileAccess> FileAccess::create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep, Error *r_error) {
const String ERROR_COMMON_PREFIX = "Error while creating temporary file";
if (!p_prefix.is_valid_filename()) {
*r_error = ERR_FILE_BAD_PATH;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid prefix.)", ERROR_COMMON_PREFIX, p_prefix));
}
if (!p_extension.is_valid_filename()) {
*r_error = ERR_FILE_BAD_PATH;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: "%s" is not a valid extension.)", ERROR_COMMON_PREFIX, p_extension));
}
const String TEMP_DIR = OS::get_singleton()->get_temp_path();
String extension = p_extension.trim_prefix(".");
uint32_t suffix_i = 0;
String path;
while (true) {
String datetime = Time::get_singleton()->get_datetime_string_from_system().replace("-", "").replace("T", "").replace(":", "");
datetime += itos(Time::get_singleton()->get_ticks_usec());
String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : "");
path = TEMP_DIR.path_join((p_prefix.is_empty() ? "" : p_prefix + "-") + suffix + (extension.is_empty() ? "" : "." + extension));
if (!DirAccess::exists(path)) {
break;
}
suffix_i += 1;
}
Error err;
{
// Create file first with WRITE mode.
// Otherwise, it would fail to open with a READ mode.
Ref<FileAccess> ret = FileAccess::open(path, FileAccess::ModeFlags::WRITE, &err);
if (err != OK) {
*r_error = err;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: could not create "%s".)", ERROR_COMMON_PREFIX, path));
}
ret->flush();
}
// Open then the temp file with the correct mode flag.
Ref<FileAccess> ret = FileAccess::open(path, p_mode_flags, &err);
if (err != OK) {
*r_error = err;
ERR_FAIL_V_MSG(Ref<FileAccess>(), vformat(R"(%s: could not open "%s".)", ERROR_COMMON_PREFIX, path));
}
if (ret.is_valid()) {
ret->_is_temp_file = true;
ret->_temp_keep_after_use = p_keep;
ret->_temp_path = ret->get_path_absolute();
}
*r_error = OK;
return ret;
}
Ref<FileAccess> FileAccess::_create_temp(int p_mode_flags, const String &p_prefix, const String &p_extension, bool p_keep) {
return create_temp(p_mode_flags, p_prefix, p_extension, p_keep, &last_file_open_error);
}
void FileAccess::_delete_temp() {
if (!_is_temp_file || _temp_keep_after_use) {
return;
}
if (!FileAccess::exists(_temp_path)) {
return;
}
DirAccess::remove_absolute(_temp_path);
}
Error FileAccess::reopen(const String &p_path, int p_mode_flags) {
return open_internal(p_path, p_mode_flags);
}
@ -126,7 +199,7 @@ Ref<FileAccess> FileAccess::_open(const String &p_path, ModeFlags p_mode_flags)
return fa;
}
Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key) {
Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key, const Vector<uint8_t> &p_iv) {
Ref<FileAccess> fa = _open(p_path, p_mode_flags);
if (fa.is_null()) {
return fa;
@ -134,7 +207,7 @@ Ref<FileAccess> FileAccess::open_encrypted(const String &p_path, ModeFlags p_mod
Ref<FileAccessEncrypted> fae;
fae.instantiate();
Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ);
Error err = fae->open_and_parse(fa, p_key, (p_mode_flags == WRITE) ? FileAccessEncrypted::MODE_WRITE_AES256 : FileAccessEncrypted::MODE_READ, true, p_iv);
last_file_open_error = err;
if (err) {
return Ref<FileAccess>();
@ -184,13 +257,17 @@ FileAccess::AccessType FileAccess::get_access_type() const {
}
String FileAccess::fix_path(const String &p_path) const {
//helper used by file accesses that use a single filesystem
// 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("uid://")) {
r_path = ResourceUID::uid_to_path(r_path);
}
if (r_path.begins_with("res://")) {
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
if (!resource_path.is_empty()) {
@ -225,59 +302,48 @@ String FileAccess::fix_path(const String &p_path) const {
}
/* these are all implemented for ease of porting, then can later be optimized */
uint8_t FileAccess::get_8() const {
uint8_t data = 0;
get_buffer(&data, sizeof(uint8_t));
return data;
}
uint16_t FileAccess::get_16() const {
uint16_t res;
uint8_t a, b;
a = get_8();
b = get_8();
uint16_t data = 0;
get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t));
if (big_endian) {
SWAP(a, b);
data = BSWAP16(data);
}
res = b;
res <<= 8;
res |= a;
return res;
return data;
}
uint32_t FileAccess::get_32() const {
uint32_t res;
uint16_t a, b;
a = get_16();
b = get_16();
uint32_t data = 0;
get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t));
if (big_endian) {
SWAP(a, b);
data = BSWAP32(data);
}
res = b;
res <<= 16;
res |= a;
return res;
return data;
}
uint64_t FileAccess::get_64() const {
uint64_t res;
uint32_t a, b;
a = get_32();
b = get_32();
uint64_t data = 0;
get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t));
if (big_endian) {
SWAP(a, b);
data = BSWAP64(data);
}
res = b;
res <<= 32;
res |= a;
return data;
}
return res;
float FileAccess::get_half() const {
return Math::half_to_float(get_16());
}
float FileAccess::get_float() const {
@ -317,7 +383,7 @@ double FileAccess::get_double() const {
String FileAccess::get_token() const {
CharString token;
char32_t c = get_8();
uint8_t c = get_8();
while (!eof_reached()) {
if (c <= ' ') {
@ -325,7 +391,7 @@ String FileAccess::get_token() const {
break;
}
} else {
token += c;
token += char(c);
}
c = get_8();
}
@ -382,14 +448,14 @@ public:
String FileAccess::get_line() const {
CharBuffer line;
char32_t c = get_8();
uint8_t c = get_8();
while (!eof_reached()) {
if (c == '\n' || c == '\0') {
if (c == '\n' || c == '\0' || get_error() != OK) {
line.push_back(0);
return String::utf8(line.get_data());
} else if (c != '\r') {
line.push_back(c);
line.push_back(char(c));
}
c = get_8();
@ -467,17 +533,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const {
return text;
}
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;
}
Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
Vector<uint8_t> data;
@ -487,10 +542,10 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
}
Error err = data.resize(p_length);
ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
ERR_FAIL_COND_V_MSG(err != OK, data, vformat("Can't resize data to %d elements.", p_length));
uint8_t *w = data.ptrw();
int64_t len = get_buffer(&w[0], p_length);
int64_t len = get_buffer(w, p_length);
if (len < p_length) {
data.resize(len);
@ -510,70 +565,60 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const {
w[len] = 0;
String s;
s.parse_utf8((const char *)w, -1, p_skip_cr);
s.parse_utf8((const char *)w, len, p_skip_cr);
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);
bool FileAccess::store_8(uint8_t p_dest) {
return store_buffer(&p_dest, sizeof(uint8_t));
}
void FileAccess::store_32(uint32_t p_dest) {
uint16_t a, b;
a = p_dest & 0xFFFF;
b = p_dest >> 16;
bool FileAccess::store_16(uint16_t p_dest) {
if (big_endian) {
SWAP(a, b);
p_dest = BSWAP16(p_dest);
}
store_16(a);
store_16(b);
return store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t));
}
void FileAccess::store_64(uint64_t p_dest) {
uint32_t a, b;
a = p_dest & 0xFFFFFFFF;
b = p_dest >> 32;
bool FileAccess::store_32(uint32_t p_dest) {
if (big_endian) {
SWAP(a, b);
p_dest = BSWAP32(p_dest);
}
store_32(a);
store_32(b);
return store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t));
}
void FileAccess::store_real(real_t p_real) {
bool FileAccess::store_64(uint64_t p_dest) {
if (big_endian) {
p_dest = BSWAP64(p_dest);
}
return store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t));
}
bool FileAccess::store_real(real_t p_real) {
if constexpr (sizeof(real_t) == 4) {
store_float(p_real);
return store_float(p_real);
} else {
store_double(p_real);
return store_double(p_real);
}
}
void FileAccess::store_float(float p_dest) {
bool FileAccess::store_half(float p_dest) {
return store_16(Math::make_half_float(p_dest));
}
bool FileAccess::store_float(float p_dest) {
MarshallFloat m;
m.f = p_dest;
store_32(m.i);
return store_32(m.i);
}
void FileAccess::store_double(double p_dest) {
bool FileAccess::store_double(double p_dest) {
MarshallDouble m;
m.d = p_dest;
store_64(m.l);
return store_64(m.l);
}
uint64_t FileAccess::get_modified_time(const String &p_file) {
@ -582,7 +627,7 @@ uint64_t FileAccess::get_modified_time(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
uint64_t mt = fa->_get_modified_time(p_file);
return mt;
@ -594,7 +639,7 @@ BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_unix_permissions(p_file);
}
@ -605,7 +650,7 @@ Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_unix_permissions(p_file, p_permissions);
return err;
@ -617,7 +662,7 @@ bool FileAccess::get_hidden_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_hidden_attribute(p_file);
}
@ -628,7 +673,7 @@ Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) {
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_hidden_attribute(p_file, p_hidden);
return err;
@ -640,7 +685,7 @@ bool FileAccess::get_read_only_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_read_only_attribute(p_file);
}
@ -651,25 +696,24 @@ Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) {
}
Ref<FileAccess> fa = create_for_path(p_file);
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_read_only_attribute(p_file, p_ro);
return err;
}
void FileAccess::store_string(const String &p_string) {
bool FileAccess::store_string(const String &p_string) {
if (p_string.length() == 0) {
return;
return true;
}
CharString cs = p_string.utf8();
store_buffer((uint8_t *)&cs[0], cs.length());
return store_buffer((uint8_t *)&cs[0], cs.length());
}
void FileAccess::store_pascal_string(const String &p_string) {
bool 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());
return store_32(cs.length()) && store_buffer((uint8_t *)&cs[0], cs.length());
}
String FileAccess::get_pascal_string() {
@ -680,24 +724,23 @@ String FileAccess::get_pascal_string() {
cs[sl] = 0;
String ret;
ret.parse_utf8(cs.ptr());
ret.parse_utf8(cs.ptr(), sl);
return ret;
}
void FileAccess::store_line(const String &p_line) {
store_string(p_line);
store_8('\n');
bool FileAccess::store_line(const String &p_line) {
return 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);
bool FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_delim) {
ERR_FAIL_COND_V(p_delim.length() != 1, false);
String line = "";
int size = p_values.size();
for (int i = 0; i < size; ++i) {
String value = p_values[i];
if (value.contains("\"") || value.contains(p_delim) || value.contains("\n")) {
if (value.contains_char('"') || value.contains(p_delim) || value.contains_char('\n')) {
value = "\"" + value.replace("\"", "\"\"") + "\"";
}
if (i < size - 1) {
@ -707,41 +750,43 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_
line += value;
}
store_line(line);
return 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]);
}
}
void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
bool FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
uint64_t len = p_buffer.size();
if (len == 0) {
return;
return true;
}
const uint8_t *r = p_buffer.ptr();
store_buffer(&r[0], len);
return store_buffer(r, len);
}
void FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
bool FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_V(!p_src && p_length > 0, false);
for (uint64_t i = 0; i < p_length; i++) {
if (unlikely(!store_8(p_src[i]))) {
return false;
}
}
return true;
}
bool FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
int len;
Error err = encode_variant(p_var, nullptr, len, p_full_objects);
ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant.");
ERR_FAIL_COND_V_MSG(err != OK, false, "Error when trying to encode Variant.");
Vector<uint8_t> buff;
buff.resize(len);
uint8_t *w = buff.ptrw();
err = encode_variant(p_var, &w[0], len, p_full_objects);
ERR_FAIL_COND_MSG(err != OK, "Error when trying to encode Variant.");
ERR_FAIL_COND_V_MSG(err != OK, false, "Error when trying to encode Variant.");
store_32(len);
store_buffer(buff);
return store_32(uint32_t(len)) && store_buffer(buff);
}
Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_error) {
@ -750,7 +795,7 @@ Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_err
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) + "'.");
ERR_FAIL_V_MSG(Vector<uint8_t>(), vformat("Can't open file from path '%s'.", String(p_path)));
}
Vector<uint8_t> data;
data.resize(f->get_length());
@ -768,7 +813,7 @@ String FileAccess::get_file_as_string(const String &p_path, Error *r_error) {
if (r_error) {
return String();
}
ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'.");
ERR_FAIL_V_MSG(String(), vformat("Can't get file as string from path '%s'.", String(p_path)));
}
String ret;
@ -859,10 +904,11 @@ String FileAccess::get_sha256(const String &p_file) {
void FileAccess::_bind_methods() {
ClassDB::bind_static_method("FileAccess", D_METHOD("open", "path", "flags"), &FileAccess::_open);
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key"), &FileAccess::open_encrypted);
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted", "path", "mode_flags", "key", "iv"), &FileAccess::open_encrypted, DEFVAL(Vector<uint8_t>()));
ClassDB::bind_static_method("FileAccess", D_METHOD("open_encrypted_with_pass", "path", "mode_flags", "pass"), &FileAccess::open_encrypted_pass);
ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0));
ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error);
ClassDB::bind_static_method("FileAccess", D_METHOD("create_temp", "mode_flags", "prefix", "extension", "keep"), &FileAccess::_create_temp, DEFVAL(""), DEFVAL(""), DEFVAL(false));
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes);
ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string);
@ -881,6 +927,7 @@ void FileAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_16"), &FileAccess::get_16);
ClassDB::bind_method(D_METHOD("get_32"), &FileAccess::get_32);
ClassDB::bind_method(D_METHOD("get_64"), &FileAccess::get_64);
ClassDB::bind_method(D_METHOD("get_half"), &FileAccess::get_half);
ClassDB::bind_method(D_METHOD("get_float"), &FileAccess::get_float);
ClassDB::bind_method(D_METHOD("get_double"), &FileAccess::get_double);
ClassDB::bind_method(D_METHOD("get_real"), &FileAccess::get_real);
@ -899,10 +946,11 @@ void FileAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("store_16", "value"), &FileAccess::store_16);
ClassDB::bind_method(D_METHOD("store_32", "value"), &FileAccess::store_32);
ClassDB::bind_method(D_METHOD("store_64", "value"), &FileAccess::store_64);
ClassDB::bind_method(D_METHOD("store_half", "value"), &FileAccess::store_half);
ClassDB::bind_method(D_METHOD("store_float", "value"), &FileAccess::store_float);
ClassDB::bind_method(D_METHOD("store_double", "value"), &FileAccess::store_double);
ClassDB::bind_method(D_METHOD("store_real", "value"), &FileAccess::store_real);
ClassDB::bind_method(D_METHOD("store_buffer", "buffer"), (void(FileAccess::*)(const Vector<uint8_t> &)) & FileAccess::store_buffer);
ClassDB::bind_method(D_METHOD("store_buffer", "buffer"), (bool(FileAccess::*)(const Vector<uint8_t> &)) & FileAccess::store_buffer);
ClassDB::bind_method(D_METHOD("store_line", "line"), &FileAccess::store_line);
ClassDB::bind_method(D_METHOD("store_csv_line", "values", "delim"), &FileAccess::store_csv_line, DEFVAL(","));
ClassDB::bind_method(D_METHOD("store_string", "string"), &FileAccess::store_string);
@ -950,3 +998,7 @@ void FileAccess::_bind_methods() {
BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID);
BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE);
}
FileAccess::~FileAccess() {
_delete_temp();
}

View file

@ -46,7 +46,7 @@ class FileAccess : public RefCounted {
GDCLASS(FileAccess, RefCounted);
public:
enum AccessType {
enum AccessType : int32_t {
ACCESS_RESOURCES,
ACCESS_USERDATA,
ACCESS_FILESYSTEM,
@ -54,14 +54,14 @@ public:
ACCESS_MAX
};
enum ModeFlags {
enum ModeFlags : int32_t {
READ = 1,
WRITE = 2,
READ_WRITE = 3,
WRITE_READ = 7,
};
enum UnixPermissionFlags {
enum UnixPermissionFlags : int32_t {
UNIX_EXECUTE_OTHER = 0x001,
UNIX_WRITE_OTHER = 0x002,
UNIX_READ_OTHER = 0x004,
@ -76,7 +76,7 @@ public:
UNIX_SET_USER_ID = 0x800,
};
enum CompressionMode {
enum CompressionMode : int32_t {
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
@ -109,6 +109,27 @@ protected:
static FileCloseFailNotify close_fail_notify;
#ifndef DISABLE_DEPRECATED
static Ref<FileAccess> _open_encrypted_bind_compat_98918(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key);
void store_8_bind_compat_78289(uint8_t p_dest);
void store_16_bind_compat_78289(uint16_t p_dest);
void store_32_bind_compat_78289(uint32_t p_dest);
void store_64_bind_compat_78289(uint64_t p_dest);
void store_buffer_bind_compat_78289(const Vector<uint8_t> &p_buffer);
void store_var_bind_compat_78289(const Variant &p_var, bool p_full_objects = false);
void store_half_bind_compat_78289(float p_dest);
void store_float_bind_compat_78289(float p_dest);
void store_double_bind_compat_78289(double p_dest);
void store_real_bind_compat_78289(real_t p_real);
void store_string_bind_compat_78289(const String &p_string);
void store_line_bind_compat_78289(const String &p_line);
void store_csv_line_bind_compat_78289(const Vector<String> &p_values, const String &p_delim = ",");
void store_pascal_string_bind_compat_78289(const String &p_string);
static void _bind_compatibility_methods();
#endif
private:
static bool backup_save;
thread_local static Error last_file_open_error;
@ -122,6 +143,13 @@ private:
static Ref<FileAccess> _open(const String &p_path, ModeFlags p_mode_flags);
bool _is_temp_file = false;
bool _temp_keep_after_use = false;
String _temp_path;
void _delete_temp();
static Ref<FileAccess> _create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false);
public:
static void set_file_close_fail_notify_callback(FileCloseFailNotify p_cbk) { close_fail_notify = p_cbk; }
@ -137,18 +165,19 @@ public:
virtual bool eof_reached() const = 0; ///< reading passed EOF
virtual uint8_t get_8() const = 0; ///< get a byte
virtual uint8_t get_8() const; ///< 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_half() const;
virtual float get_float() const;
virtual double get_double() const;
virtual real_t get_real() const;
Variant get_var(bool p_allow_objects = false) const;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children.
Vector<uint8_t> get_buffer(int64_t p_length) const;
virtual String get_line() const;
virtual String get_token() const;
@ -157,6 +186,7 @@ public:
virtual String get_as_utf8_string(bool p_skip_cr = false) 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.
@ -168,26 +198,27 @@ public:
virtual Error resize(int64_t p_length) = 0;
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 bool store_8(uint8_t p_dest); ///< store a byte
virtual bool store_16(uint16_t p_dest); ///< store 16 bits uint
virtual bool store_32(uint32_t p_dest); ///< store 32 bits uint
virtual bool 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 bool store_half(float p_dest);
virtual bool store_float(float p_dest);
virtual bool store_double(double p_dest);
virtual bool 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 bool store_string(const String &p_string);
virtual bool store_line(const String &p_line);
virtual bool store_csv_line(const Vector<String> &p_values, const String &p_delim = ",");
virtual void store_pascal_string(const String &p_string);
virtual bool 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
void store_buffer(const Vector<uint8_t> &p_buffer);
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children.
bool store_buffer(const Vector<uint8_t> &p_buffer);
void store_var(const Variant &p_var, bool p_full_objects = false);
bool store_var(const Variant &p_var, bool p_full_objects = false);
virtual void close() = 0;
@ -198,8 +229,9 @@ public:
static Ref<FileAccess> create(AccessType p_access); /// Create a file access (for the current platform) this is the only portable way of accessing files.
static Ref<FileAccess> create_for_path(const String &p_path);
static Ref<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 Ref<FileAccess> create_temp(int p_mode_flags, const String &p_prefix = "", const String &p_extension = "", bool p_keep = false, Error *r_error = nullptr);
static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key);
static Ref<FileAccess> open_encrypted(const String &p_path, ModeFlags p_mode_flags, const Vector<uint8_t> &p_key, const Vector<uint8_t> &p_iv = Vector<uint8_t>());
static Ref<FileAccess> open_encrypted_pass(const String &p_path, ModeFlags p_mode_flags, const String &p_pass);
static Ref<FileAccess> open_compressed(const String &p_path, ModeFlags p_mode_flags, CompressionMode p_compress_mode = COMPRESSION_FASTLZ);
static Error get_open_error();
@ -215,8 +247,8 @@ public:
static bool get_read_only_attribute(const String &p_file);
static Error set_read_only_attribute(const String &p_file, bool p_ro);
static void set_backup_save(bool p_enable) { backup_save = p_enable; };
static bool is_backup_save_enabled() { return backup_save; };
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);
@ -233,8 +265,9 @@ public:
create_func[p_access] = _create_builtin<T>;
}
public:
FileAccess() {}
virtual ~FileAccess() {}
virtual ~FileAccess();
};
VARIANT_ENUM_CAST(FileAccess::CompressionMode);

View file

@ -30,8 +30,6 @@
#include "file_access_compressed.h"
#include "core/string/print_string.h"
void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) {
magic = p_magic.ascii().get_data();
magic = (magic + " ").substr(0, 4);
@ -40,25 +38,13 @@ void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_
block_size = p_block_size;
}
#define WRITE_FIT(m_bytes) \
{ \
if (write_pos + (m_bytes) > write_max) { \
write_max = write_pos + (m_bytes); \
} \
if (write_max > write_buffer_size) { \
write_buffer_size = next_power_of_2(write_max); \
buffer.resize(write_buffer_size); \
write_ptr = buffer.ptrw(); \
} \
}
Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {
f = p_base;
cmode = (Compression::Mode)f->get_32();
block_size = f->get_32();
if (block_size == 0) {
f.unref();
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted.");
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Can't open compressed file '%s' with block size 0, it is corrupted.", p_base->get_path()));
}
read_total = f->get_32();
uint32_t bc = (read_total / block_size) + 1;
@ -137,7 +123,7 @@ void FileAccessCompressed::_close() {
f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //write header 4
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
f->store_32(uint32_t(write_max)); //max amount of data written 4
uint32_t bc = (write_max / block_size) + 1;
for (uint32_t i = 0; i < bc; i++) {
@ -159,7 +145,7 @@ void FileAccessCompressed::_close() {
f->seek(16); //ok write block sizes
for (uint32_t i = 0; i < bc; i++) {
f->store_32(block_sizes[i]);
f->store_32(uint32_t(block_sizes[i]));
}
f->seek_end();
f->store_buffer((const uint8_t *)mgc.get_data(), mgc.length()); //magic at the end too
@ -260,38 +246,6 @@ bool FileAccessCompressed::eof_reached() const {
}
}
uint8_t FileAccessCompressed::get_8() const {
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
if (at_end) {
read_eof = true;
return 0;
}
uint8_t ret = read_ptr[read_pos];
read_pos++;
if (read_pos >= read_block_size) {
read_block++;
if (read_block < read_block_count) {
//read another block of compressed data
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt.");
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
read_pos = 0;
} else {
read_block--;
at_end = true;
}
}
return ret;
}
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.is_null(), -1, "File must be opened before use.");
@ -341,12 +295,25 @@ void FileAccessCompressed::flush() {
// compressed files keep data in memory till close()
}
void FileAccessCompressed::store_8(uint8_t p_dest) {
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
bool FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_V_MSG(f.is_null(), false, "File must be opened before use.");
ERR_FAIL_COND_V_MSG(!writing, false, "File has not been opened in write mode.");
WRITE_FIT(1);
write_ptr[write_pos++] = p_dest;
if (write_pos + (p_length) > write_max) {
write_max = write_pos + (p_length);
}
if (write_max > write_buffer_size) {
write_buffer_size = next_power_of_2(write_max);
ERR_FAIL_COND_V(buffer.resize(write_buffer_size) != OK, false);
write_ptr = buffer.ptrw();
}
if (p_length) {
memcpy(write_ptr + write_pos, p_src, p_length);
}
write_pos += p_length;
return true;
}
bool FileAccessCompressed::file_exists(const String &p_name) {

View file

@ -83,14 +83,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists

View file

@ -31,13 +31,10 @@
#include "file_access_encrypted.h"
#include "core/crypto/crypto_core.h"
#include "core/string/print_string.h"
#include "core/variant/variant.h"
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic, const Vector<uint8_t> &p_iv) {
ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, vformat("Can't open file while another file from path '%s' is open.", file->get_path_absolute()));
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
@ -49,6 +46,16 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u
writing = true;
file = p_base;
key = p_key;
if (p_iv.is_empty()) {
iv.resize(16);
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
Error err = rng.get_random_bytes(iv.ptrw(), 16);
ERR_FAIL_COND_V(err != OK, err);
} else {
ERR_FAIL_COND_V(p_iv.size() != 16, ERR_INVALID_PARAMETER);
iv = p_iv;
}
} else if (p_mode == MODE_READ) {
writing = false;
@ -63,10 +70,8 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u
p_base->get_buffer(md5d, 16);
length = p_base->get_64();
unsigned char iv[16];
for (int i = 0; i < 16; i++) {
iv[i] = p_base->get_8();
}
iv.resize(16);
p_base->get_buffer(iv.ptrw(), 16);
base = p_base->get_position();
ERR_FAIL_COND_V(p_base->get_length() < base + length, ERR_FILE_CORRUPT);
@ -83,7 +88,7 @@ Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<u
CryptoCore::AESContext ctx;
ctx.set_encode_key(key.ptrw(), 256); // Due to the nature of CFB, same key schedule is used for both encryption and decryption!
ctx.decrypt_cfb(ds, iv, data.ptrw(), data.ptrw());
ctx.decrypt_cfb(ds, iv.ptrw(), data.ptrw(), data.ptrw());
}
data.resize(length);
@ -145,14 +150,9 @@ void FileAccessEncrypted::_close() {
file->store_buffer(hash, 16);
file->store_64(data.size());
file->store_buffer(iv.ptr(), 16);
unsigned char iv[16];
for (int i = 0; i < 16; i++) {
iv[i] = Math::rand() % 256;
file->store_8(iv[i]);
}
ctx.encrypt_cfb(len, iv, compressed.ptrw(), compressed.ptrw());
ctx.encrypt_cfb(len, iv.ptrw(), compressed.ptrw(), compressed.ptrw());
file->store_buffer(compressed.ptr(), compressed.size());
data.clear();
@ -162,7 +162,7 @@ void FileAccessEncrypted::_close() {
}
bool FileAccessEncrypted::is_open() const {
return file != nullptr;
return file.is_valid();
}
String FileAccessEncrypted::get_path() const {
@ -206,26 +206,19 @@ bool FileAccessEncrypted::eof_reached() const {
return eofed;
}
uint8_t FileAccessEncrypted::get_8() const {
ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
if (pos >= get_length()) {
eofed = true;
uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
if (!p_length) {
return 0;
}
uint8_t b = data[pos];
pos++;
return b;
}
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.");
ERR_FAIL_NULL_V(p_dst, -1);
uint64_t to_copy = MIN(p_length, get_length() - pos);
for (uint64_t i = 0; i < to_copy; i++) {
p_dst[i] = data[pos++];
}
memcpy(p_dst, data.ptr() + pos, to_copy);
pos += to_copy;
if (to_copy < p_length) {
eofed = true;
@ -238,21 +231,23 @@ Error FileAccessEncrypted::get_error() const {
return eofed ? ERR_FILE_EOF : OK;
}
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);
bool FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_V_MSG(!writing, false, "File has not been opened in write mode.");
if (pos < get_length()) {
for (uint64_t i = 0; i < p_length; i++) {
store_8(p_src[i]);
}
} else if (pos == get_length()) {
data.resize(pos + p_length);
for (uint64_t i = 0; i < p_length; i++) {
data.write[pos + i] = p_src[i];
}
pos += p_length;
if (!p_length) {
return true;
}
ERR_FAIL_NULL_V(p_src, false);
if (pos + p_length >= get_length()) {
ERR_FAIL_COND_V(data.resize(pos + p_length) != OK, false);
}
memcpy(data.ptrw() + pos, p_src, p_length);
pos += p_length;
return true;
}
void FileAccessEncrypted::flush() {
@ -261,18 +256,6 @@ void FileAccessEncrypted::flush() {
// 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 write mode.");
if (pos < get_length()) {
data.write[pos] = p_dest;
pos++;
} else if (pos == get_length()) {
data.push_back(p_dest);
pos++;
}
}
bool FileAccessEncrypted::file_exists(const String &p_name) {
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
if (fa.is_null()) {

View file

@ -37,13 +37,14 @@
class FileAccessEncrypted : public FileAccess {
public:
enum Mode {
enum Mode : int32_t {
MODE_READ,
MODE_WRITE_AES256,
MODE_MAX
};
private:
Vector<uint8_t> iv;
Vector<uint8_t> key;
bool writing = false;
Ref<FileAccess> file;
@ -57,9 +58,11 @@ private:
void _close();
public:
Error open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true);
Error open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic = true, const Vector<uint8_t> &p_iv = Vector<uint8_t>());
Error open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode);
Vector<uint8_t> get_iv() const { return iv; }
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open
@ -73,15 +76,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists

View file

@ -31,8 +31,6 @@
#include "file_access_memory.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/templates/rb_map.h"
static HashMap<String, Vector<uint8_t>> *files = nullptr;
@ -85,7 +83,7 @@ Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
//name = DirAccess::normalize_path(name);
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, "Can't find file '" + p_path + "'.");
ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", p_path));
data = E->value.ptrw();
length = E->value.size();
@ -122,18 +120,12 @@ bool FileAccessMemory::eof_reached() const {
return pos >= length;
}
uint8_t FileAccessMemory::get_8() const {
uint8_t ret = 0;
if (pos < length) {
ret = data[pos];
}
++pos;
return ret;
}
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
if (!p_length) {
return 0;
}
ERR_FAIL_NULL_V(p_dst, -1);
ERR_FAIL_NULL_V(data, -1);
uint64_t left = length - pos;
@ -157,20 +149,20 @@ void FileAccessMemory::flush() {
ERR_FAIL_NULL(data);
}
void FileAccessMemory::store_8(uint8_t p_byte) {
ERR_FAIL_NULL(data);
ERR_FAIL_COND(pos >= length);
data[pos++] = p_byte;
}
bool FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (!p_length) {
return true;
}
ERR_FAIL_NULL_V(p_src, false);
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");
}
memcpy(&data[pos], p_src, write);
pos += write;
ERR_FAIL_COND_V_MSG(write < p_length, false, "Writing less data than requested.");
return true;
}

View file

@ -55,16 +55,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists

View file

@ -35,8 +35,6 @@
#include "core/os/os.h"
#include "core/version.h"
#include <stdio.h>
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)) {
@ -48,7 +46,7 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
}
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) {
String simplified_path = p_path.simplify_path();
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
bool exists = files.has(pmd5);
@ -68,13 +66,11 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
if (!exists) {
//search for dir
String p = simplified_path.replace_first("res://", "");
// Search for directory.
PackedDir *cd = root;
if (p.contains("/")) { //in a subdir
Vector<String> ds = p.get_base_dir().split("/");
if (simplified_path.contains_char('/')) { // In a subdirectory.
Vector<String> ds = simplified_path.get_base_dir().split("/");
for (int j = 0; j < ds.size(); j++) {
if (!cd->subdirs.has(ds[j])) {
@ -89,19 +85,79 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
}
String filename = simplified_path.get_file();
// Don't add as a file if the path points to a directory
// Don't add as a file if the path points to a directory.
if (!filename.is_empty()) {
cd->files.insert(filename);
}
}
}
void PackedData::remove_path(const String &p_path) {
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
if (!files.has(pmd5)) {
return;
}
// Search for directory.
PackedDir *cd = root;
if (simplified_path.contains_char('/')) { // In a subdirectory.
Vector<String> ds = simplified_path.get_base_dir().split("/");
for (int j = 0; j < ds.size(); j++) {
if (!cd->subdirs.has(ds[j])) {
return; // Subdirectory does not exist, do not bother creating.
} else {
cd = cd->subdirs[ds[j]];
}
}
}
cd->files.erase(simplified_path.get_file());
files.erase(pmd5);
}
void PackedData::add_pack_source(PackSource *p_source) {
if (p_source != nullptr) {
sources.push_back(p_source);
}
}
uint8_t *PackedData::get_file_hash(const String &p_path) {
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
if (!E) {
return nullptr;
}
return E->value.md5;
}
HashSet<String> PackedData::get_file_paths() const {
HashSet<String> file_paths;
_get_file_paths(root, root->name, file_paths);
return file_paths;
}
void PackedData::_get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const {
for (const String &E : p_dir->files) {
r_paths.insert(p_parent_dir.path_join(E));
}
for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) {
_get_file_paths(E.value, p_parent_dir.path_join(E.key), r_paths);
}
}
void PackedData::clear() {
files.clear();
_free_packed_dirs(root);
root = memnew(PackedDir);
}
PackedData *PackedData::singleton = nullptr;
PackedData::PackedData() {
@ -207,8 +263,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
uint32_t ver_minor = f->get_32();
f->get_32(); // patch number, not used for validation.
ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + ".");
ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, vformat("Pack version unsupported: %d.", version));
ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.", ver_major, ver_minor));
uint32_t pack_flags = f->get_32();
uint64_t file_base = f->get_64();
@ -251,15 +307,19 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
cs[sl] = 0;
String path;
path.parse_utf8(cs.ptr());
path.parse_utf8(cs.ptr(), sl);
uint64_t ofs = file_base + f->get_64();
uint64_t ofs = f->get_64();
uint64_t size = f->get_64();
uint8_t md5[16];
f->get_buffer(md5, 16);
uint32_t flags = f->get_32();
PackedData::get_singleton()->add_path(p_path, path, ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
if (flags & PACK_FILE_REMOVAL) { // The file was removed.
PackedData::get_singleton()->remove_path(path);
} else {
PackedData::get_singleton()->add_path(p_path, path, file_base + ofs + p_offset, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED));
}
}
return true;
@ -271,6 +331,44 @@ Ref<FileAccess> PackedSourcePCK::get_file(const String &p_path, PackedData::Pack
//////////////////////////////////////////////////////////////////
bool PackedSourceDirectory::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
// 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 directories.");
if (p_path != "res://") {
return false;
}
add_directory(p_path, p_replace_files);
return true;
}
Ref<FileAccess> PackedSourceDirectory::get_file(const String &p_path, PackedData::PackedFile *p_file) {
Ref<FileAccess> ret = FileAccess::create_for_path(p_path);
ret->reopen(p_path, FileAccess::READ);
return ret;
}
void PackedSourceDirectory::add_directory(const String &p_path, bool p_replace_files) {
Ref<DirAccess> da = DirAccess::open(p_path);
if (da.is_null()) {
return;
}
da->set_include_hidden(true);
for (const String &file_name : da->get_files()) {
String file_path = p_path.path_join(file_name);
uint8_t md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
PackedData::get_singleton()->add_path(p_path, file_path, 0, 0, md5, this, p_replace_files, false);
}
for (const String &sub_dir_name : da->get_directories()) {
String sub_dir_path = p_path.path_join(sub_dir_name);
add_directory(sub_dir_path, p_replace_files);
}
}
//////////////////////////////////////////////////////////////////
Error FileAccessPack::open_internal(const String &p_path, int p_mode_flags) {
ERR_PRINT("Can't open pack-referenced file.");
return ERR_UNAVAILABLE;
@ -313,17 +411,6 @@ bool FileAccessPack::eof_reached() const {
return eof;
}
uint8_t FileAccessPack::get_8() const {
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
if (pos >= pf.size) {
eof = true;
return 0;
}
pos++;
return f->get_8();
}
uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@ -366,12 +453,8 @@ void FileAccessPack::flush() {
ERR_FAIL();
}
void FileAccessPack::store_8(uint8_t p_dest) {
ERR_FAIL();
}
void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
bool FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_V(false);
}
bool FileAccessPack::file_exists(const String &p_name) {
@ -385,7 +468,7 @@ void FileAccessPack::close() {
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) :
pf(p_file),
f(FileAccess::open(pf.pack, FileAccess::READ)) {
ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(pf.pack) + "'.");
ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
f->seek(pf.offset);
off = pf.offset;
@ -393,7 +476,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
if (pf.encrypted) {
Ref<FileAccessEncrypted> fae;
fae.instantiate();
ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
Vector<uint8_t> key;
key.resize(32);
@ -402,7 +485,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
}
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
f = fae;
off = 0;
}
@ -543,8 +626,6 @@ String DirAccessPack::get_current_dir(bool p_include_drive) const {
}
bool DirAccessPack::file_exists(String p_file) {
p_file = fix_path(p_file);
PackedData::PackedDir *pd = _find_dir(p_file.get_base_dir());
if (!pd) {
return false;
@ -553,8 +634,6 @@ bool DirAccessPack::file_exists(String p_file) {
}
bool DirAccessPack::dir_exists(String p_dir) {
p_dir = fix_path(p_dir);
return _find_dir(p_dir) != nullptr;
}

View file

@ -36,7 +36,6 @@
#include "core/string/print_string.h"
#include "core/templates/hash_set.h"
#include "core/templates/list.h"
#include "core/templates/rb_map.h"
// Godot's packed file magic header ("GDPC" in ASCII).
#define PACK_HEADER_MAGIC 0x43504447
@ -49,7 +48,8 @@ enum PackFlags {
};
enum PackFileFlags {
PACK_FILE_ENCRYPTED = 1 << 0
PACK_FILE_ENCRYPTED = 1 << 0,
PACK_FILE_REMOVAL = 1 << 1,
};
class PackSource;
@ -107,10 +107,14 @@ private:
bool disabled = false;
void _free_packed_dirs(PackedDir *p_dir);
void _get_file_paths(PackedDir *p_dir, const String &p_parent_dir, HashSet<String> &r_paths) const;
public:
void add_pack_source(PackSource *p_source);
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 remove_path(const String &p_path);
uint8_t *get_file_hash(const String &p_path);
HashSet<String> get_file_paths() const;
void set_disabled(bool p_disabled) { disabled = p_disabled; }
_FORCE_INLINE_ bool is_disabled() const { return disabled; }
@ -118,6 +122,8 @@ public:
static PackedData *get_singleton() { return singleton; }
Error add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset);
void clear();
_FORCE_INLINE_ Ref<FileAccess> try_open_path(const String &p_path);
_FORCE_INLINE_ bool has_path(const String &p_path);
@ -141,6 +147,14 @@ public:
virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) override;
};
class PackedSourceDirectory : public PackSource {
void add_directory(const String &p_path, bool p_replace_files);
public:
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override;
virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) override;
};
class FileAccessPack : public FileAccess {
PackedData::PackedFile pf;
@ -169,8 +183,6 @@ public:
virtual bool eof_reached() const override;
virtual uint8_t get_8() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual void set_big_endian(bool p_big_endian) override;
@ -179,9 +191,7 @@ public:
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override;
@ -191,21 +201,18 @@ public:
};
Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
String simplified_path = p_path.simplify_path();
String simplified_path = p_path.simplify_path().trim_prefix("res://");
PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
if (!E) {
return nullptr; //not found
}
if (E->value.offset == 0) {
return nullptr; //was erased
return nullptr; // Not found.
}
return E->value.src->get_file(p_path, &E->value);
}
bool PackedData::has_path(const String &p_path) {
return files.has(PathMD5(p_path.simplify_path().md5_buffer()));
return files.has(PathMD5(p_path.simplify_path().trim_prefix("res://").md5_buffer()));
}
bool PackedData::has_directory(const String &p_path) {

View file

@ -116,7 +116,7 @@ void ZipArchive::close_handle(unzFile p_file) const {
}
unzFile ZipArchive::get_file_handle(const String &p_file) const {
ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, vformat("File '%s' doesn't exist.", p_file));
File file = files[p_file];
zlib_filefunc_def io;
@ -136,7 +136,7 @@ unzFile ZipArchive::get_file_handle(const String &p_file) const {
io.free_mem = godot_free;
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
ERR_FAIL_NULL_V_MSG(pkg, nullptr, vformat("Cannot open file '%s'.", packages[file.package].filename));
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
unzClose(pkg);
@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const {
return at_eof;
}
uint8_t FileAccessZip::get_8() const {
uint8_t ret = 0;
get_buffer(&ret, 1);
return ret;
}
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_NULL_V(zfile, -1);
@ -328,8 +322,8 @@ void FileAccessZip::flush() {
ERR_FAIL();
}
void FileAccessZip::store_8(uint8_t p_dest) {
ERR_FAIL();
bool FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_V(false);
}
bool FileAccessZip::file_exists(const String &p_name) {

View file

@ -34,12 +34,9 @@
#ifdef MINIZIP_ENABLED
#include "core/io/file_access_pack.h"
#include "core/templates/rb_map.h"
#include "thirdparty/minizip/unzip.h"
#include <stdlib.h>
class ZipArchive : public PackSource {
public:
struct File {
@ -95,14 +92,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists

View file

@ -42,9 +42,9 @@ const char *HTTPClient::_methods[METHOD_MAX] = {
"PATCH"
};
HTTPClient *HTTPClient::create() {
HTTPClient *HTTPClient::create(bool p_notify_postinitialize) {
if (_create) {
return _create();
return _create(p_notify_postinitialize);
}
return nullptr;
}
@ -100,9 +100,9 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
Error HTTPClient::verify_headers(const Vector<String> &p_headers) {
for (int i = 0; i < p_headers.size(); i++) {
String sanitized = p_headers[i].strip_edges();
ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty.");
ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER,
"Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]);
ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, vformat("Invalid HTTP header at index %d: empty.", i));
ERR_FAIL_COND_V_MSG(sanitized.find_char(':') < 1, ERR_INVALID_PARAMETER,
vformat("Invalid HTTP header at index %d: String must contain header-value pair, delimited by ':', but was: '%s'.", i, p_headers[i]));
}
return OK;
@ -113,7 +113,7 @@ Dictionary HTTPClient::_get_response_headers_as_dictionary() {
get_response_headers(&rh);
Dictionary ret;
for (const String &s : rh) {
int sp = s.find(":");
int sp = s.find_char(':');
if (sp == -1) {
continue;
}

View file

@ -158,12 +158,12 @@ protected:
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 HTTPClient *(*_create)(bool p_notify_postinitialize);
static void _bind_methods();
public:
static HTTPClient *create();
static HTTPClient *create(bool p_notify_postinitialize = true);
String query_string_from_dict(const Dictionary &p_dict);
Error verify_headers(const Vector<String> &p_headers);

View file

@ -35,8 +35,8 @@
#include "core/io/stream_peer_tls.h"
#include "core/version.h"
HTTPClient *HTTPClientTCP::_create_func() {
return memnew(HTTPClientTCP);
HTTPClient *HTTPClientTCP::_create_func(bool p_notify_postinitialize) {
return static_cast<HTTPClient *>(ClassDB::creator<HTTPClientTCP>(p_notify_postinitialize));
}
Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_options) {
@ -484,7 +484,7 @@ Error HTTPClientTCP::poll() {
// End of response, parse.
response_str.push_back(0);
String response;
response.parse_utf8((const char *)response_str.ptr());
response.parse_utf8((const char *)response_str.ptr(), response_str.size());
Vector<String> responses = response.split("\n");
body_size = -1;
chunked = false;
@ -508,11 +508,11 @@ Error HTTPClientTCP::poll() {
continue;
}
if (s.begins_with("content-length:")) {
body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int();
body_size = s.substr(s.find_char(':') + 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();
String encoding = header.substr(header.find_char(':') + 1, header.length()).strip_edges();
if (encoding == "chunked") {
chunked = true;
}
@ -662,15 +662,16 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() {
chunk_left -= rec;
if (chunk_left == 0) {
if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') {
const int chunk_size = chunk.size();
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);
ret.resize(chunk_size - 2);
uint8_t *w = ret.ptrw();
memcpy(w, chunk.ptr(), chunk.size() - 2);
memcpy(w, chunk.ptr(), chunk_size - 2);
chunk.clear();
}
@ -792,6 +793,6 @@ HTTPClientTCP::HTTPClientTCP() {
request_buffer.instantiate();
}
HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func;
HTTPClient *(*HTTPClient::_create)(bool p_notify_postinitialize) = HTTPClientTCP::_create_func;
#endif // WEB_ENABLED

View file

@ -76,7 +76,7 @@ private:
Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received);
public:
static HTTPClient *_create_func();
static HTTPClient *_create_func(bool p_notify_postinitialize);
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,6 @@
#include "core/io/resource.h"
#include "core/math/color.h"
#include "core/math/rect2.h"
/**
* Image storage class. This is used to store an image in user memory, as well as
@ -43,12 +42,17 @@
class Image;
// Function pointer prototypes.
typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img);
typedef Vector<uint8_t> (*SavePNGBufferFunc)(const Ref<Image> &p_img);
typedef Error (*SaveJPGFunc)(const String &p_path, const Ref<Image> &p_img, float p_quality);
typedef Vector<uint8_t> (*SaveJPGBufferFunc)(const Ref<Image> &p_img, float p_quality);
typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
typedef Ref<Image> (*ScalableImageMemLoadFunc)(const uint8_t *p_data, int p_size, float p_scale);
typedef Error (*SaveWebPFunc)(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
typedef Vector<uint8_t> (*SaveWebPBufferFunc)(const Ref<Image> &p_img, const bool p_lossy, const float p_quality);
@ -59,57 +63,48 @@ class Image : public Resource {
GDCLASS(Image, Resource);
public:
static SavePNGFunc save_png_func;
static SaveJPGFunc save_jpg_func;
static SaveEXRFunc save_exr_func;
static SavePNGBufferFunc save_png_buffer_func;
static SaveEXRBufferFunc save_exr_buffer_func;
static SaveJPGBufferFunc save_jpg_buffer_func;
static SaveWebPFunc save_webp_func;
static SaveWebPBufferFunc save_webp_buffer_func;
enum {
MAX_WIDTH = (1 << 24), // force a limit somehow
MAX_HEIGHT = (1 << 24), // force a limit somehow
MAX_PIXELS = 268435456
MAX_WIDTH = (1 << 24), // Force a limit somehow.
MAX_HEIGHT = (1 << 24), // Force a limit somehow.
MAX_PIXELS = 268435456 // 16384 ^ 2
};
enum Format {
FORMAT_L8, //luminance
FORMAT_LA8, //luminance-alpha
enum Format : int32_t {
FORMAT_L8, // Luminance
FORMAT_LA8, // Luminance-Alpha
FORMAT_R8,
FORMAT_RG8,
FORMAT_RGB8,
FORMAT_RGBA8,
FORMAT_RGBA4444,
FORMAT_RGB565,
FORMAT_RF, //float
FORMAT_RF, // Float
FORMAT_RGF,
FORMAT_RGBF,
FORMAT_RGBAF,
FORMAT_RH, //half float
FORMAT_RH, // Half
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_ETC, //etc1
FORMAT_ETC2_R11, //etc2
FORMAT_ETC2_R11S, //signed, NOT srgb.
FORMAT_DXT1, // BC1
FORMAT_DXT3, // BC2
FORMAT_DXT5, // BC3
FORMAT_RGTC_R, // BC4
FORMAT_RGTC_RG, // BC5
FORMAT_BPTC_RGBA, // BC7
FORMAT_BPTC_RGBF, // BC6 Signed
FORMAT_BPTC_RGBFU, // BC6 Unsigned
FORMAT_ETC, // ETC1
FORMAT_ETC2_R11,
FORMAT_ETC2_R11S, // Signed, NOT srgb.
FORMAT_ETC2_RG11,
FORMAT_ETC2_RG11S,
FORMAT_ETC2_RG11S, // Signed, NOT srgb.
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_ETC2_RA_AS_RG, // ETC2 RGBA with a RA-RG swizzle for normal maps.
FORMAT_DXT5_RA_AS_RG, // BC3 with a RA-RG swizzle for normal maps.
FORMAT_ASTC_4x4,
FORMAT_ASTC_4x4_HDR,
FORMAT_ASTC_8x8,
@ -118,17 +113,18 @@ public:
};
static const char *format_names[FORMAT_MAX];
enum Interpolation {
INTERPOLATE_NEAREST,
INTERPOLATE_BILINEAR,
INTERPOLATE_CUBIC,
INTERPOLATE_TRILINEAR,
INTERPOLATE_LANCZOS,
/* INTERPOLATE_TRICUBIC, */
/* INTERPOLATE GAUSS */
// INTERPOLATE_TRICUBIC,
// INTERPOLATE_GAUSS
};
//this is used for compression
// Used for obtaining optimal compression quality.
enum UsedChannels {
USED_CHANNELS_L,
USED_CHANNELS_LA,
@ -137,13 +133,66 @@ public:
USED_CHANNELS_RGB,
USED_CHANNELS_RGBA,
};
//some functions provided by something else
// ASTC supports block formats other than 4x4.
enum ASTCFormat {
ASTC_FORMAT_4x4,
ASTC_FORMAT_8x8,
};
enum RoughnessChannel {
ROUGHNESS_CHANNEL_R,
ROUGHNESS_CHANNEL_G,
ROUGHNESS_CHANNEL_B,
ROUGHNESS_CHANNEL_A,
ROUGHNESS_CHANNEL_L,
};
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,
};
enum CompressMode {
COMPRESS_S3TC,
COMPRESS_ETC,
COMPRESS_ETC2,
COMPRESS_BPTC,
COMPRESS_ASTC,
COMPRESS_MAX,
};
enum CompressSource {
COMPRESS_SOURCE_GENERIC,
COMPRESS_SOURCE_SRGB,
COMPRESS_SOURCE_NORMAL,
COMPRESS_SOURCE_MAX,
};
enum AlphaMode {
ALPHA_NONE,
ALPHA_BIT,
ALPHA_BLEND
};
// External saver function pointers.
static SavePNGFunc save_png_func;
static SaveJPGFunc save_jpg_func;
static SaveEXRFunc save_exr_func;
static SaveWebPFunc save_webp_func;
static SavePNGBufferFunc save_png_buffer_func;
static SaveEXRBufferFunc save_exr_buffer_func;
static SaveJPGBufferFunc save_jpg_buffer_func;
static SaveWebPBufferFunc save_webp_buffer_func;
// External loader function pointers.
static ImageMemLoadFunc _png_mem_loader_func;
static ImageMemLoadFunc _png_mem_unpacker_func;
static ImageMemLoadFunc _jpg_mem_loader_func;
@ -153,30 +202,37 @@ public:
static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func;
static ImageMemLoadFunc _ktx_mem_loader_func;
// External VRAM compression function pointers.
static void (*_image_compress_bc_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_bptc_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_etc1_func)(Image *);
static void (*_image_compress_etc2_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format);
static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels);
static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels);
// External VRAM decompression function pointers.
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 void (*_image_decompress_astc)(Image *);
// External packer function pointers.
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> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*basis_universal_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size);
_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();
@ -187,15 +243,12 @@ private:
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;
}
void _copy_internals_from(const Image &p_image);
_FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data
_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);
_FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; // Get where the mipmap begins in data.
static int64_t _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;
@ -212,6 +265,8 @@ private:
Error _load_from_buffer(const Vector<uint8_t> &p_array, ImageMemLoadFunc p_loader);
_FORCE_INLINE_ void _generate_mipmap_from_format(Image::Format p_format, const uint8_t *p_src, uint8_t *p_dst, uint32_t p_width, uint32_t p_height, bool p_renormalize = false);
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);
@ -222,52 +277,32 @@ private:
static void renormalize_rgbe9995(uint32_t *p_rgb);
public:
int get_width() const; ///< Get image width
int get_height() const; ///< Get image height
int get_width() const;
int get_height() const;
Size2i get_size() const;
bool has_mipmaps() const;
int get_mipmap_count() const;
/**
* Convert the image to another format, conversion only to raw byte format
*/
// 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;
/**
* Get where the mipmap begins in data.
*/
// Get where the mipmap begins in data.
int64_t get_mipmap_offset(int p_mipmap) const;
void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const;
void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const;
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.
*/
// 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
*/
// 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);
@ -277,34 +312,20 @@ public:
void flip_x();
void flip_y();
/**
* Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1)
*/
// Generate a mipmap chain of 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
void normalize();
/**
* Creates new internal image data of a given size and format. Current image will be lost.
*/
// Creates new internal image data of a given size and format. Current image will be lost.
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format);
void initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
void initialize_data(const char **p_xpm);
/**
* returns true when the image is empty (0,0) in size
*/
// Returns true when the image is empty (0,0) in size.
bool is_empty() const;
Vector<uint8_t> get_data() const;
@ -324,27 +345,14 @@ public:
static Ref<Image> create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
void set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data);
/**
* 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() = default; // Create an empty image.
Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format); // Create an empty image of a specific size and format.
Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector<uint8_t> &p_data); // Import an image of a specific size and format from a byte vector.
Image(const uint8_t *p_mem_png_jpg, int p_len = -1); // Import either a png or jpg from a pointer.
Image(const char **p_xpm); // Import an XPM image.
~Image() {}
enum AlphaMode {
ALPHA_NONE,
ALPHA_BIT,
ALPHA_BLEND
};
AlphaMode detect_alpha() const;
bool is_invisible() const;
@ -359,21 +367,6 @@ public:
static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap);
static int64_t 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_ETC,
COMPRESS_ETC2,
COMPRESS_BPTC,
COMPRESS_ASTC,
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, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error decompress();
@ -383,11 +376,14 @@ public:
void fix_alpha_edges();
void premultiply_alpha();
void srgb_to_linear();
void linear_to_srgb();
void normal_map_to_xy();
Ref<Image> rgbe_to_srgb();
Ref<Image> get_image_from_mipmap(int p_mipmap) const;
void bump_map_to_normal_map(float bump_scale = 1.0);
bool detect_signed(bool p_include_mips = true) const;
void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest);
void blend_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
@ -398,9 +394,8 @@ public:
Rect2i get_used_rect() const;
Ref<Image> get_region(const Rect2i &p_area) const;
static void set_compress_bc_func(void (*p_compress_func)(Image *, UsedChannels));
static void set_compress_bptc_func(void (*p_compress_func)(Image *, UsedChannels));
static String get_format_name(Format p_format);
static uint32_t get_format_component_mask(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);
@ -416,9 +411,6 @@ public:
void convert_ra_rgba8_to_rg();
void convert_rgba8_to_bgra8();
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) const;
@ -437,14 +429,7 @@ public:
void set_as_black();
void copy_internals_from(const Ref<Image> &p_image) {
ERR_FAIL_COND_MSG(p_image.is_null(), "Cannot copy image internals: invalid Image object.");
format = p_image->format;
width = p_image->width;
height = p_image->height;
mipmaps = p_image->mipmaps;
data = p_image->data;
}
void copy_internals_from(const Ref<Image> &p_image);
Dictionary compute_image_metrics(const Ref<Image> p_compared_image, bool p_luma_metric = true);
};

View file

@ -30,8 +30,6 @@
#include "image_loader.h"
#include "core/string/print_string.h"
void ImageFormatLoader::_bind_methods() {
BIND_BITFIELD_FLAG(FLAG_NONE);
BIND_BITFIELD_FLAG(FLAG_FORCE_LINEAR);
@ -82,15 +80,16 @@ void ImageFormatLoaderExtension::_bind_methods() {
Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<FileAccess> p_custom, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
ERR_FAIL_COND_V_MSG(p_image.is_null(), ERR_INVALID_PARAMETER, "Can't load an image: invalid Image object.");
const String file = ResourceUID::ensure_path(p_file);
Ref<FileAccess> f = p_custom;
if (f.is_null()) {
Error err;
f = FileAccess::open(p_file, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'.");
f = FileAccess::open(file, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(f.is_null(), err, vformat("Error opening file '%s'.", file));
}
String extension = p_file.get_extension();
String extension = file.get_extension();
for (int i = 0; i < loader.size(); i++) {
if (!loader[i]->recognize(extension)) {
@ -98,7 +97,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
}
Error err = loader.write[i]->load_image(p_image, f, p_flags, p_scale);
if (err != OK) {
ERR_PRINT("Error loading image: " + p_file);
ERR_PRINT(vformat("Error loading image: '%s'.", file));
}
if (err != ERR_FILE_UNRECOGNIZED) {

View file

@ -51,7 +51,7 @@ struct _IP_ResolverPrivate {
response.clear();
type = IP::TYPE_NONE;
hostname = "";
};
}
QueueItem() {
clear();
@ -81,17 +81,17 @@ struct _IP_ResolverPrivate {
continue;
}
mutex.lock();
MutexLock lock(mutex);
List<IPAddress> response;
String hostname = queue[i].hostname;
IP::Type type = queue[i].type;
mutex.unlock();
lock.temp_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);
lock.temp_relock();
// Could have been completed by another function, or deleted.
if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
continue;
@ -131,21 +131,22 @@ PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, 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 as long as the result is valid.
if (res.size()) {
resolver->cache[key] = res;
{
MutexLock lock(resolver->mutex);
if (resolver->cache.has(key)) {
res = resolver->cache[key];
} else {
// This should be run unlocked so the resolver thread can keep resolving
// other requests.
lock.temp_unlock();
_resolve_hostname(res, p_hostname, p_type);
lock.temp_relock();
// We might be overriding another result, but we don't care as long as the result is valid.
if (res.size()) {
resolver->cache[key] = res;
}
}
}
resolver->mutex.unlock();
PackedStringArray result;
for (const IPAddress &E : res) {
@ -200,7 +201,7 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
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.");
ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return IPAddress();
}
@ -219,7 +220,7 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
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.");
ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return Array();
}

View file

@ -34,7 +34,6 @@ IPAddress::operator Variant() const {
return operator String();
}*/
#include <stdio.h>
#include <string.h>
IPAddress::operator String() const {
@ -202,7 +201,7 @@ IPAddress::IPAddress(const String &p_string) {
// Wildcard (not a valid IP)
wildcard = true;
} else if (p_string.contains(":")) {
} else if (p_string.contains_char(':')) {
// IPv6
_parse_ipv6(p_string);
valid = true;

File diff suppressed because it is too large Load diff

View file

@ -80,6 +80,9 @@ class JSON : public Resource {
static Error _parse_object(Dictionary &object, const char32_t *p_str, int &index, int p_len, int &line, int p_depth, String &r_err_str);
static Error _parse_string(const String &p_json, Variant &r_ret, String &r_err_str, int &r_err_line);
static Variant _from_native(const Variant &p_variant, bool p_full_objects, int p_depth);
static Variant _to_native(const Variant &p_json, bool p_allow_objects, int p_depth);
protected:
static void _bind_methods();
@ -90,10 +93,18 @@ public:
static String stringify(const Variant &p_var, const String &p_indent = "", bool p_sort_keys = true, bool p_full_precision = false);
static Variant parse_string(const String &p_json_string);
inline Variant get_data() const { return data; }
_FORCE_INLINE_ static Variant from_native(const Variant &p_variant, bool p_full_objects = false) {
return _from_native(p_variant, p_full_objects, 0);
}
_FORCE_INLINE_ static Variant to_native(const Variant &p_json, bool p_allow_objects = false) {
return _to_native(p_json, p_allow_objects, 0);
}
void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; }
_FORCE_INLINE_ Variant get_data() const { return data; }
_FORCE_INLINE_ int get_error_line() const { return err_line; }
_FORCE_INLINE_ String get_error_message() const { return err_str; }
};
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
@ -102,6 +113,10 @@ public:
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
// Treat JSON as a text file, do not generate a `*.json.uid` file.
virtual ResourceUID::ID get_resource_uid(const String &p_path) const override { return ResourceUID::INVALID_ID; }
virtual bool has_custom_uid_support() const override { return true; }
};
class ResourceFormatSaverJSON : public ResourceFormatSaver {

View file

@ -30,14 +30,17 @@
#include "logger.h"
#include "core/config/project_settings.h"
#include "core/core_globals.h"
#include "core/io/dir_access.h"
#include "core/os/os.h"
#include "core/os/time.h"
#include "core/string/print_string.h"
#include "core/templates/rb_set.h"
#include "modules/modules_enabled.gen.h" // For regex.
#ifdef MODULE_REGEX_ENABLED
#include "modules/regex/regex.h"
#else
class RegEx : public RefCounted {};
#endif // MODULE_REGEX_ENABLED
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
#define sprintf sprintf_s
@ -84,11 +87,7 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c
err_details = p_code;
}
if (p_editor_notify) {
logf_error("%s: %s\n", err_type, err_details);
} else {
logf_error("USER %s: %s\n", err_type, err_details);
}
logf_error("%s: %s\n", err_type, err_details);
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
}
@ -131,7 +130,9 @@ void RotatedFileLogger::clear_old_backups() {
da->list_dir_begin();
String f = da->get_next();
HashSet<String> backups;
// backups is a RBSet because it guarantees that iterating on it is done in sorted order.
// RotatedFileLogger depends on this behavior to delete the oldest log file first.
RBSet<String> backups;
while (!f.is_empty()) {
if (!da->current_is_dir() && f.begins_with(basename) && f.get_extension() == extension && f != base_path.get_file()) {
backups.insert(f);
@ -140,12 +141,12 @@ void RotatedFileLogger::clear_old_backups() {
}
da->list_dir_end();
if (backups.size() > (uint32_t)max_backups) {
if (backups.size() > max_backups) {
// since backups are appended with timestamp and Set iterates them in sorted order,
// first backups are the oldest
int to_delete = backups.size() - max_backups;
for (HashSet<String>::Iterator E = backups.begin(); E && to_delete > 0; ++E, --to_delete) {
da->remove(*E);
for (RBSet<String>::Element *E = backups.front(); E && to_delete > 0; E = E->next(), --to_delete) {
da->remove(E->get());
}
}
}

View file

@ -34,13 +34,11 @@
#include "core/io/file_access.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
#include "modules/modules_enabled.gen.h" // For regex.
#ifdef MODULE_REGEX_ENABLED
#include "modules/regex/regex.h"
#endif // MODULE_REGEX_ENABLED
#include <stdarg.h>
class RegEx;
class Logger {
protected:
bool should_log(bool p_err);
@ -90,9 +88,7 @@ class RotatedFileLogger : public Logger {
void clear_old_backups();
void rotate_file();
#ifdef MODULE_REGEX_ENABLED
Ref<RegEx> strip_ansi_regex;
#endif // MODULE_REGEX_ENABLED
public:
explicit RotatedFileLogger(const String &p_base_path, int p_max_files = 10);

View file

@ -33,8 +33,7 @@
#include "core/io/resource_loader.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
#include "core/string/print_string.h"
#include "core/variant/container_type_validate.h"
#include <limits.h>
#include <stdio.h>
@ -69,10 +68,25 @@ ObjectID EncodedObjectAsID::get_object_id() const {
// For `Variant::ARRAY`.
// Occupies bits 16 and 17.
#define HEADER_DATA_FIELD_TYPED_ARRAY_MASK (0b11 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_NONE (0b00 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN (0b01 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME (0b10 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT (0b11 << 16)
#define HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT 16
// For `Variant::DICTIONARY`.
// Occupies bits 16 and 17.
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_MASK (0b11 << 16)
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT 16
// Occupies bits 18 and 19.
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_MASK (0b11 << 18)
#define HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT 18
enum ContainerTypeKind {
CONTAINER_TYPE_KIND_NONE = 0b00,
CONTAINER_TYPE_KIND_BUILTIN = 0b01,
CONTAINER_TYPE_KIND_CLASS_NAME = 0b10,
CONTAINER_TYPE_KIND_SCRIPT = 0b11,
};
#define GET_CONTAINER_TYPE_KIND(m_header, m_field) \
((ContainerTypeKind)(((m_header) & HEADER_DATA_FIELD_##m_field##_MASK) >> HEADER_DATA_FIELD_##m_field##_SHIFT))
static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r_string) {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
@ -80,7 +94,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
int32_t strlen = decode_uint32(buf);
int32_t pad = 0;
// Handle padding
// Handle padding.
if (strlen % 4) {
pad = 4 - strlen % 4;
}
@ -88,7 +102,7 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
buf += 4;
len -= 4;
// Ensure buffer is big enough
// Ensure buffer is big enough.
ERR_FAIL_ADD_OF(strlen, pad, ERR_FILE_EOF);
ERR_FAIL_COND_V(strlen < 0 || strlen + pad > len, ERR_FILE_EOF);
@ -96,10 +110,10 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
ERR_FAIL_COND_V(str.parse_utf8((const char *)buf, strlen) != OK, ERR_INVALID_DATA);
r_string = str;
// Add padding
// Add padding.
strlen += pad;
// Update buffer pos, left data count, and return size
// Update buffer pos, left data count, and return size.
buf += strlen;
len -= strlen;
if (r_len) {
@ -109,6 +123,65 @@ static Error _decode_string(const uint8_t *&buf, int &len, int *r_len, String &r
return OK;
}
static Error _decode_container_type(const uint8_t *&buf, int &len, int *r_len, bool p_allow_objects, ContainerTypeKind p_type_kind, ContainerType &r_type) {
switch (p_type_kind) {
case CONTAINER_TYPE_KIND_NONE: {
return OK;
} break;
case CONTAINER_TYPE_KIND_BUILTIN: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t bt = decode_uint32(buf);
buf += 4;
len -= 4;
if (r_len) {
(*r_len) += 4;
}
ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
r_type.builtin_type = (Variant::Type)bt;
if (!p_allow_objects && r_type.builtin_type == Variant::OBJECT) {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
case CONTAINER_TYPE_KIND_CLASS_NAME: {
String str;
Error err = _decode_string(buf, len, r_len, str);
if (err) {
return err;
}
r_type.builtin_type = Variant::OBJECT;
if (p_allow_objects) {
r_type.class_name = str;
} else {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
case CONTAINER_TYPE_KIND_SCRIPT: {
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
r_type.builtin_type = Variant::OBJECT;
if (p_allow_objects) {
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
r_type.script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(r_type.script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
r_type.class_name = r_type.script->get_instance_base_type();
} else {
r_type.class_name = EncodedObjectAsID::get_class_static();
}
return OK;
} break;
}
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid container type kind."); // Future proofing.
}
Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_objects, int p_depth) {
ERR_FAIL_COND_V_MSG(p_depth > Variant::MAX_RECURSION_DEPTH, ERR_OUT_OF_MEMORY, "Variant is too deep. Bailing.");
const uint8_t *buf = p_buffer;
@ -126,7 +199,7 @@ 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.
// 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 (header & HEADER_TYPE_MASK) {
@ -144,7 +217,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
case Variant::INT: {
if (header & HEADER_DATA_FLAG_64) {
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
int64_t val = decode_uint64(buf);
int64_t val = int64_t(decode_uint64(buf));
r_variant = val;
if (r_len) {
(*r_len) += 8;
@ -152,7 +225,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} else {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t val = decode_uint32(buf);
int32_t val = int32_t(decode_uint32(buf));
r_variant = val;
if (r_len) {
(*r_len) += 4;
@ -188,7 +261,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
// math types
// Math types.
case Variant::VECTOR2: {
Vector2 val;
if (header & HEADER_DATA_FLAG_64) {
@ -539,7 +612,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = val;
} break;
// misc types
// Misc types.
case Variant::COLOR: {
ERR_FAIL_COND_V(len < 4 * 4, ERR_INVALID_DATA);
Color val;
@ -568,7 +642,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
int32_t strlen = decode_uint32(buf);
if (strlen & 0x80000000) {
//new format
// New format.
ERR_FAIL_COND_V(len < 12, ERR_INVALID_DATA);
Vector<StringName> names;
Vector<StringName> subnames;
@ -607,8 +681,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = NodePath(names, subnames, np_flags & 1);
} else {
//old format, just a string
// Old format, just a string.
ERR_FAIL_V(ERR_INVALID_DATA);
}
@ -698,9 +771,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
if (str == "script" && value.get_type() != Variant::NIL) {
ERR_FAIL_COND_V_MSG(value.get_type() != Variant::STRING, ERR_INVALID_DATA, "Invalid value for \"script\" property, expected script path as String.");
String path = value;
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path \"%s\".", path));
Ref<Script> script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path \"%s\".", path));
obj->set_script(script);
} else {
obj->set(str, value);
@ -731,9 +804,30 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
r_variant = Signal(id, StringName(name));
} break;
case Variant::DICTIONARY: {
ContainerType key_type;
{
ContainerTypeKind key_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_KEY);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, key_type_kind, key_type);
if (err) {
return err;
}
}
ContainerType value_type;
{
ContainerTypeKind value_type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_DICTIONARY_VALUE);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, value_type_kind, value_type);
if (err) {
return err;
}
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
// bool shared = count&0x80000000;
//bool shared = count & 0x80000000;
count &= 0x7FFFFFFF;
buf += 4;
@ -743,7 +837,10 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
Dictionary d;
Dictionary dict;
if (key_type.builtin_type != Variant::NIL || value_type.builtin_type != Variant::NIL) {
dict.set_typed(key_type, value_type);
}
for (int i = 0; i < count; i++) {
Variant key, value;
@ -767,75 +864,27 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += used;
}
d[key] = value;
dict[key] = value;
}
r_variant = d;
r_variant = dict;
} break;
case Variant::ARRAY: {
Variant::Type builtin_type = Variant::VARIANT_MAX;
StringName class_name;
Ref<Script> script;
ContainerType type;
switch (header & HEADER_DATA_FIELD_TYPED_ARRAY_MASK) {
case HEADER_DATA_FIELD_TYPED_ARRAY_NONE:
break; // Untyped array.
case HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t bt = decode_uint32(buf);
buf += 4;
len -= 4;
if (r_len) {
(*r_len) += 4;
}
ERR_FAIL_INDEX_V(bt, Variant::VARIANT_MAX, ERR_INVALID_DATA);
builtin_type = (Variant::Type)bt;
if (!p_allow_objects && builtin_type == Variant::OBJECT) {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME: {
String str;
Error err = _decode_string(buf, len, r_len, str);
if (err) {
return err;
}
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
class_name = str;
} else {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
case HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT: {
String path;
Error err = _decode_string(buf, len, r_len, path);
if (err) {
return err;
}
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
script = ResourceLoader::load(path, "Script");
ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
class_name = script->get_instance_base_type();
} else {
class_name = EncodedObjectAsID::get_class_static();
}
} break;
default:
ERR_FAIL_V(ERR_INVALID_DATA); // Future proofing.
{
ContainerTypeKind type_kind = GET_CONTAINER_TYPE_KIND(header, TYPED_ARRAY);
Error err = _decode_container_type(buf, len, r_len, p_allow_objects, type_kind, type);
if (err) {
return err;
}
}
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
// bool shared = count&0x80000000;
//bool shared = count & 0x80000000;
count &= 0x7FFFFFFF;
buf += 4;
@ -845,29 +894,29 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
(*r_len) += 4; // Size of count number.
}
Array varr;
if (builtin_type != Variant::VARIANT_MAX) {
varr.set_typed(builtin_type, class_name, script);
Array array;
if (type.builtin_type != Variant::NIL) {
array.set_typed(type);
}
for (int i = 0; i < count; i++) {
int used = 0;
Variant v;
Error err = decode_variant(v, buf, len, &used, p_allow_objects, p_depth + 1);
Variant elem;
Error err = decode_variant(elem, buf, len, &used, p_allow_objects, p_depth + 1);
ERR_FAIL_COND_V_MSG(err != OK, err, "Error when trying to decode Variant.");
buf += used;
len -= used;
varr.push_back(v);
array.push_back(elem);
if (r_len) {
(*r_len) += used;
}
}
r_variant = varr;
r_variant = array;
} break;
// arrays
// Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t count = decode_uint32(buf);
@ -906,7 +955,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<int32_t> data;
if (count) {
//const int*rbuf=(const int*)buf;
//const int *rbuf = (const int *)buf;
data.resize(count);
int32_t *w = data.ptrw();
for (int32_t i = 0; i < count; i++) {
@ -930,7 +979,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<int64_t> data;
if (count) {
//const int*rbuf=(const int*)buf;
//const int *rbuf = (const int *)buf;
data.resize(count);
int64_t *w = data.ptrw();
for (int64_t i = 0; i < count; i++) {
@ -954,7 +1003,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<float> data;
if (count) {
//const float*rbuf=(const float*)buf;
//const float *rbuf = (const float *)buf;
data.resize(count);
float *w = data.ptrw();
for (int32_t i = 0; i < count; i++) {
@ -1265,13 +1314,50 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) {
r_len += 4 + utf8.length();
while (r_len % 4) {
r_len++; //pad
r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
}
}
static void _encode_container_type_header(const ContainerType &p_type, uint32_t &header, uint32_t p_shift, bool p_full_objects) {
if (p_type.builtin_type != Variant::NIL) {
if (p_type.script.is_valid()) {
header |= (p_full_objects ? CONTAINER_TYPE_KIND_SCRIPT : CONTAINER_TYPE_KIND_CLASS_NAME) << p_shift;
} else if (p_type.class_name != StringName()) {
header |= CONTAINER_TYPE_KIND_CLASS_NAME << p_shift;
} else {
// No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
header |= CONTAINER_TYPE_KIND_BUILTIN << p_shift;
}
}
}
static Error _encode_container_type(const ContainerType &p_type, uint8_t *&buf, int &r_len, bool p_full_objects) {
if (p_type.builtin_type != Variant::NIL) {
if (p_type.script.is_valid()) {
if (p_full_objects) {
String path = p_type.script->get_path();
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for a container type.");
_encode_string(path, buf, r_len);
} else {
_encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
}
} else if (p_type.class_name != StringName()) {
_encode_string(p_full_objects ? p_type.class_name.operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
} else {
// No need to check `p_full_objects` since `class_name` should be non-empty for `builtin_type == Variant::OBJECT`.
if (buf) {
encode_uint32(p_type.builtin_type, buf);
buf += 4;
}
r_len += 4;
}
}
return OK;
}
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 infinite recursion detected. Bailing.");
uint8_t *buf = r_buffer;
@ -1310,20 +1396,14 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
header |= HEADER_DATA_FLAG_OBJECT_AS_ID;
}
} break;
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
_encode_container_type_header(dict.get_key_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_KEY_SHIFT, p_full_objects);
_encode_container_type_header(dict.get_value_type(), header, HEADER_DATA_FIELD_TYPED_DICTIONARY_VALUE_SHIFT, p_full_objects);
} break;
case Variant::ARRAY: {
Array array = p_variant;
if (array.is_typed()) {
Ref<Script> script = array.get_typed_script();
if (script.is_valid()) {
header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else if (array.get_typed_class_name() != StringName()) {
header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME;
} else {
// No need to check `p_full_objects` since for `Variant::OBJECT`
// `array.get_typed_class_name()` should be non-empty.
header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN;
}
}
const Array array = p_variant;
_encode_container_type_header(array.get_element_type(), header, HEADER_DATA_FIELD_TYPED_ARRAY_SHIFT, p_full_objects);
} break;
#ifdef REAL_T_IS_DOUBLE
case Variant::VECTOR2:
@ -1344,7 +1424,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
#endif // REAL_T_IS_DOUBLE
default: {
} // nothing to do at this stage
// Nothing to do at this stage.
} break;
}
if (buf) {
@ -1355,7 +1436,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
switch (p_variant.get_type()) {
case Variant::NIL: {
//nothing to do
// Nothing to do.
} break;
case Variant::BOOL: {
if (buf) {
@ -1367,15 +1448,15 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::INT: {
if (header & HEADER_DATA_FLAG_64) {
//64 bits
// 64 bits.
if (buf) {
encode_uint64(p_variant.operator int64_t(), buf);
encode_uint64(p_variant.operator uint64_t(), buf);
}
r_len += 8;
} else {
if (buf) {
encode_uint32(p_variant.operator int32_t(), buf);
encode_uint32(p_variant.operator uint32_t(), buf);
}
r_len += 4;
@ -1401,7 +1482,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
case Variant::NODE_PATH: {
NodePath np = p_variant;
if (buf) {
encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); //for compatibility with the old format
encode_uint32(uint32_t(np.get_name_count()) | 0x80000000, buf); // For compatibility with the old format.
encode_uint32(np.get_subname_count(), buf + 4);
uint32_t np_flags = 0;
if (np.is_absolute()) {
@ -1451,7 +1532,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
// math types
// Math types.
case Variant::VECTOR2: {
if (buf) {
Vector2 v2 = p_variant;
@ -1635,7 +1716,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
// misc types
// Misc types.
case Variant::COLOR: {
if (buf) {
Color c = p_variant;
@ -1746,29 +1827,43 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 8;
} break;
case Variant::DICTIONARY: {
Dictionary d = p_variant;
const Dictionary dict = p_variant;
{
Error err = _encode_container_type(dict.get_key_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
}
{
Error err = _encode_container_type(dict.get_value_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
}
if (buf) {
encode_uint32(uint32_t(d.size()), buf);
encode_uint32(uint32_t(dict.size()), buf);
buf += 4;
}
r_len += 4;
List<Variant> keys;
d.get_key_list(&keys);
dict.get_key_list(&keys);
for (const Variant &E : keys) {
for (const Variant &key : keys) {
int len;
Error err = encode_variant(E, buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(key, 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);
ERR_FAIL_NULL_V(v, ERR_BUG);
err = encode_variant(*v, buf, len, p_full_objects, p_depth + 1);
const Variant *value = dict.getptr(key);
ERR_FAIL_NULL_V(value, ERR_BUG);
err = encode_variant(*value, 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;
@ -1779,29 +1874,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
} break;
case Variant::ARRAY: {
Array array = p_variant;
const Array array = p_variant;
if (array.is_typed()) {
Variant variant = array.get_typed_script();
Ref<Script> script = variant;
if (script.is_valid()) {
if (p_full_objects) {
String path = script->get_path();
ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type.");
_encode_string(path, buf, r_len);
} else {
_encode_string(EncodedObjectAsID::get_class_static(), buf, r_len);
}
} else if (array.get_typed_class_name() != StringName()) {
_encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len);
} else {
// No need to check `p_full_objects` since for `Variant::OBJECT`
// `array.get_typed_class_name()` should be non-empty.
if (buf) {
encode_uint32(array.get_typed_builtin(), buf);
buf += 4;
}
r_len += 4;
{
Error err = _encode_container_type(array.get_element_type(), buf, r_len, p_full_objects);
if (err) {
return err;
}
}
@ -1811,9 +1889,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
r_len += 4;
for (const Variant &var : array) {
for (const Variant &elem : array) {
int len;
Error err = encode_variant(var, buf, len, p_full_objects, p_depth + 1);
Error err = encode_variant(elem, buf, len, p_full_objects, p_depth + 1);
ERR_FAIL_COND_V(err, err);
ERR_FAIL_COND_V(len % 4, ERR_BUG);
if (buf) {
@ -1823,7 +1901,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
} break;
// arrays
// Packed arrays.
case Variant::PACKED_BYTE_ARRAY: {
Vector<uint8_t> data = p_variant;
int datalen = data.size();
@ -1939,7 +2018,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
r_len += 4 + utf8.length() + 1;
while (r_len % 4) {
r_len++; //pad
r_len++; // Pad.
if (buf) {
*(buf++) = 0;
}
@ -2057,9 +2136,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
}
Vector<float> vector3_to_float32_array(const Vector3 *vecs, size_t count) {
// We always allocate a new array, and we don't memcpy.
// We also don't consider returning a pointer to the passed vectors when sizeof(real_t) == 4.
// One reason is that we could decide to put a 4th component in Vector3 for SIMD/mobile performance,
// We always allocate a new array, and we don't `memcpy()`.
// We also don't consider returning a pointer to the passed vectors when `sizeof(real_t) == 4`.
// One reason is that we could decide to put a 4th component in `Vector3` for SIMD/mobile performance,
// which would cause trouble with these optimizations.
Vector<float> floats;
if (count == 0) {

View file

@ -84,6 +84,12 @@ static inline unsigned int encode_uint32(uint32_t p_uint, uint8_t *p_arr) {
return sizeof(uint32_t);
}
static inline unsigned int encode_half(float p_float, uint8_t *p_arr) {
encode_uint16(Math::make_half_float(p_float), p_arr);
return sizeof(uint16_t);
}
static inline unsigned int encode_float(float p_float, uint8_t *p_arr) {
MarshallFloat mf;
mf.f = p_float;
@ -172,6 +178,10 @@ static inline uint32_t decode_uint32(const uint8_t *p_arr) {
return u;
}
static inline float decode_half(const uint8_t *p_arr) {
return Math::half_to_float(decode_uint16(p_arr));
}
static inline float decode_float(const uint8_t *p_arr) {
MarshallFloat mf;
mf.i = decode_uint32(p_arr);

View file

@ -74,6 +74,10 @@ bool MissingResource::is_recording_properties() const {
return recording_properties;
}
String MissingResource::get_save_class() const {
return original_class;
}
void MissingResource::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingResource::set_original_class);
ClassDB::bind_method(D_METHOD("get_original_class"), &MissingResource::get_original_class);

View file

@ -57,6 +57,8 @@ public:
void set_recording_properties(bool p_enable);
bool is_recording_properties() const;
virtual String get_save_class() const override;
MissingResource();
};

View file

@ -41,13 +41,13 @@ protected:
public:
static NetSocket *create();
enum PollType {
enum PollType : int32_t {
POLL_TYPE_IN,
POLL_TYPE_OUT,
POLL_TYPE_IN_OUT
};
enum Type {
enum Type : int32_t {
TYPE_NONE,
TYPE_TCP,
TYPE_UDP,
@ -76,6 +76,8 @@ public:
virtual void set_reuse_address_enabled(bool p_enabled) = 0;
virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) = 0;
virtual ~NetSocket() {}
};
#endif // NET_SOCKET_H

View file

@ -36,7 +36,7 @@
class PackedDataContainer : public Resource {
GDCLASS(PackedDataContainer, Resource);
enum {
enum : uint32_t {
TYPE_DICT = 0xFFFFFFFF,
TYPE_ARRAY = 0xFFFFFFFE,
};

View file

@ -299,7 +299,7 @@ Ref<StreamPeer> PacketPeerStream::get_stream_peer() const {
void PacketPeerStream::set_input_buffer_max_size(int p_max_size) {
ERR_FAIL_COND_MSG(p_max_size < 0, "Max size of input buffer size cannot be smaller than 0.");
//warning may lose packets
// WARNING: May lose packets.
ERR_FAIL_COND_MSG(ring_buffer.data_left(), "Buffer in use, resizing would cause loss of data.");
ring_buffer.resize(nearest_shift(next_power_of_2(p_max_size + 4)) - 1);
input_buffer.resize(next_power_of_2(p_max_size + 4));

Some files were not shown because too many files have changed in this diff Show more