initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
240
core/SCsub
Normal file
240
core/SCsub
Normal file
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
import os
|
||||
|
||||
import core_builders
|
||||
|
||||
import methods
|
||||
|
||||
Import("env")
|
||||
|
||||
env.core_sources = []
|
||||
|
||||
# Add required thirdparty code.
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
env_thirdparty = env.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
|
||||
# Misc thirdparty code: header paths are hardcoded, we don't need to append
|
||||
# to the include path (saves a few chars on the compiler invocation for touchy MSVC...)
|
||||
thirdparty_misc_dir = "#thirdparty/misc/"
|
||||
thirdparty_misc_sources = [
|
||||
# C sources
|
||||
"fastlz.c",
|
||||
"r128.c",
|
||||
"smaz.c",
|
||||
# C++ sources
|
||||
"pcg.cpp",
|
||||
"polypartition.cpp",
|
||||
"smolv.cpp",
|
||||
]
|
||||
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
|
||||
|
||||
# Brotli
|
||||
if env["brotli"] and env["builtin_brotli"]:
|
||||
thirdparty_brotli_dir = "#thirdparty/brotli/"
|
||||
thirdparty_brotli_sources = [
|
||||
"common/constants.c",
|
||||
"common/context.c",
|
||||
"common/dictionary.c",
|
||||
"common/platform.c",
|
||||
"common/shared_dictionary.c",
|
||||
"common/transform.c",
|
||||
"dec/bit_reader.c",
|
||||
"dec/decode.c",
|
||||
"dec/huffman.c",
|
||||
"dec/state.c",
|
||||
]
|
||||
thirdparty_brotli_sources = [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
|
||||
|
||||
env_thirdparty.Prepend(CPPEXTPATH=[thirdparty_brotli_dir + "include"])
|
||||
env.Prepend(CPPEXTPATH=[thirdparty_brotli_dir + "include"])
|
||||
|
||||
if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
|
||||
env_thirdparty.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
|
||||
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
|
||||
|
||||
# Clipper2 Thirdparty source files used for polygon and polyline boolean operations.
|
||||
if env["builtin_clipper2"]:
|
||||
thirdparty_clipper_dir = "#thirdparty/clipper2/"
|
||||
thirdparty_clipper_sources = [
|
||||
"src/clipper.engine.cpp",
|
||||
"src/clipper.offset.cpp",
|
||||
"src/clipper.rectclip.cpp",
|
||||
]
|
||||
thirdparty_clipper_sources = [thirdparty_clipper_dir + file for file in thirdparty_clipper_sources]
|
||||
|
||||
env_thirdparty.Prepend(CPPEXTPATH=[thirdparty_clipper_dir + "include"])
|
||||
env.Prepend(CPPEXTPATH=[thirdparty_clipper_dir + "include"])
|
||||
|
||||
env_thirdparty.Append(CPPDEFINES=["CLIPPER2_ENABLED"])
|
||||
env.Append(CPPDEFINES=["CLIPPER2_ENABLED"])
|
||||
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_clipper_sources)
|
||||
|
||||
# Zlib library, can be unbundled
|
||||
if env["builtin_zlib"]:
|
||||
thirdparty_zlib_dir = "#thirdparty/zlib/"
|
||||
thirdparty_zlib_sources = [
|
||||
"adler32.c",
|
||||
"compress.c",
|
||||
"crc32.c",
|
||||
"deflate.c",
|
||||
"inffast.c",
|
||||
"inflate.c",
|
||||
"inftrees.c",
|
||||
"trees.c",
|
||||
"uncompr.c",
|
||||
"zutil.c",
|
||||
]
|
||||
thirdparty_zlib_sources = [thirdparty_zlib_dir + file for file in thirdparty_zlib_sources]
|
||||
|
||||
env_thirdparty.Prepend(CPPEXTPATH=[thirdparty_zlib_dir])
|
||||
# Needs to be available in main env too
|
||||
env.Prepend(CPPEXTPATH=[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)
|
||||
|
||||
# Minizip library, could be unbundled in theory
|
||||
# However, our version has some custom modifications, so it won't compile with the system one
|
||||
thirdparty_minizip_dir = "#thirdparty/minizip/"
|
||||
thirdparty_minizip_sources = ["ioapi.c", "unzip.c", "zip.c"]
|
||||
thirdparty_minizip_sources = [thirdparty_minizip_dir + file for file in thirdparty_minizip_sources]
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_minizip_sources)
|
||||
|
||||
# Zstd library, can be unbundled
|
||||
if env["builtin_zstd"]:
|
||||
thirdparty_zstd_dir = "#thirdparty/zstd/"
|
||||
thirdparty_zstd_sources = [
|
||||
"common/debug.c",
|
||||
"common/entropy_common.c",
|
||||
"common/error_private.c",
|
||||
"common/fse_decompress.c",
|
||||
"common/pool.c",
|
||||
"common/threading.c",
|
||||
"common/xxhash.c",
|
||||
"common/zstd_common.c",
|
||||
"compress/fse_compress.c",
|
||||
"compress/hist.c",
|
||||
"compress/huf_compress.c",
|
||||
"compress/zstd_compress.c",
|
||||
"compress/zstd_double_fast.c",
|
||||
"compress/zstd_fast.c",
|
||||
"compress/zstd_lazy.c",
|
||||
"compress/zstd_ldm.c",
|
||||
"compress/zstd_opt.c",
|
||||
"compress/zstd_preSplit.c",
|
||||
"compress/zstdmt_compress.c",
|
||||
"compress/zstd_compress_literals.c",
|
||||
"compress/zstd_compress_sequences.c",
|
||||
"compress/zstd_compress_superblock.c",
|
||||
"decompress/huf_decompress.c",
|
||||
"decompress/zstd_ddict.c",
|
||||
"decompress/zstd_decompress_block.c",
|
||||
"decompress/zstd_decompress.c",
|
||||
]
|
||||
if (
|
||||
env["platform"] in ["android", "ios", "linuxbsd", "macos", "windows"]
|
||||
and env["arch"] == "x86_64"
|
||||
and not env.msvc
|
||||
):
|
||||
# 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]
|
||||
|
||||
env_thirdparty.Prepend(CPPEXTPATH=[thirdparty_zstd_dir, thirdparty_zstd_dir + "common"])
|
||||
env_thirdparty.Append(CPPDEFINES=["ZSTD_STATIC_LINKING_ONLY"])
|
||||
env.Prepend(CPPEXTPATH=thirdparty_zstd_dir)
|
||||
# Also needed in main env includes will trigger warnings
|
||||
env.Append(CPPDEFINES=["ZSTD_STATIC_LINKING_ONLY"])
|
||||
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_zstd_sources)
|
||||
|
||||
|
||||
env.core_sources += thirdparty_obj
|
||||
|
||||
|
||||
# Godot source files
|
||||
env.add_source_files(env.core_sources, "*.cpp")
|
||||
|
||||
# Generate disabled classes
|
||||
env.CommandNoCache(
|
||||
"disabled_classes.gen.h", env.Value(env.disabled_classes), env.Run(core_builders.disabled_class_builder)
|
||||
)
|
||||
|
||||
# Generate version info
|
||||
env.CommandNoCache(
|
||||
"version_generated.gen.h",
|
||||
env.Value(methods.get_version_info(env.module_version_string)),
|
||||
env.Run(core_builders.version_info_builder),
|
||||
)
|
||||
|
||||
# Generate version hash
|
||||
gen_hash = env.CommandNoCache(
|
||||
"version_hash.gen.cpp", env.Value(methods.get_git_info()), env.Run(core_builders.version_hash_builder)
|
||||
)
|
||||
env.add_source_files(env.core_sources, gen_hash)
|
||||
|
||||
# Generate AES256 script encryption key
|
||||
encryption_key = os.environ.get("SCRIPT_AES256_ENCRYPTION_KEY")
|
||||
if encryption_key:
|
||||
print(
|
||||
"\n*** IMPORTANT: Compiling Godot with custom `SCRIPT_AES256_ENCRYPTION_KEY` set as environment variable."
|
||||
"\n*** Make sure to use templates compiled with this key when exporting a project with encryption.\n"
|
||||
)
|
||||
gen_encrypt = env.CommandNoCache(
|
||||
"script_encryption_key.gen.cpp",
|
||||
env.Value(encryption_key),
|
||||
env.Run(core_builders.encryption_key_builder),
|
||||
)
|
||||
env.add_source_files(env.core_sources, gen_encrypt)
|
||||
|
||||
# Certificates
|
||||
env.CommandNoCache(
|
||||
"#core/io/certs_compressed.gen.h",
|
||||
["#thirdparty/certs/ca-certificates.crt", env.Value(env["builtin_certs"]), env.Value(env["system_certs_path"])],
|
||||
env.Run(core_builders.make_certs_header),
|
||||
)
|
||||
|
||||
# Authors
|
||||
env.CommandNoCache("#core/authors.gen.h", "#AUTHORS.md", env.Run(core_builders.make_authors_header))
|
||||
|
||||
# Donors
|
||||
env.CommandNoCache("#core/donors.gen.h", "#DONORS.md", env.Run(core_builders.make_donors_header))
|
||||
|
||||
# License
|
||||
env.CommandNoCache(
|
||||
"#core/license.gen.h", ["#COPYRIGHT.txt", "#LICENSE.txt"], env.Run(core_builders.make_license_header)
|
||||
)
|
||||
|
||||
# Chain load SCsubs
|
||||
SConscript("os/SCsub")
|
||||
SConscript("math/SCsub")
|
||||
SConscript("crypto/SCsub")
|
||||
SConscript("io/SCsub")
|
||||
SConscript("debugger/SCsub")
|
||||
SConscript("input/SCsub")
|
||||
SConscript("variant/SCsub")
|
||||
SConscript("extension/SCsub")
|
||||
SConscript("object/SCsub")
|
||||
SConscript("templates/SCsub")
|
||||
SConscript("string/SCsub")
|
||||
SConscript("config/SCsub")
|
||||
SConscript("error/SCsub")
|
||||
|
||||
|
||||
# Build it all as a library
|
||||
lib = env.add_library("core", env.core_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
|
||||
# Needed to force rebuilding the core files when the thirdparty code is updated.
|
||||
env.Depends(lib, thirdparty_obj)
|
||||
8
core/config/SCsub
Normal file
8
core/config/SCsub
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env_config = env.Clone()
|
||||
|
||||
env_config.add_source_files(env.core_sources, "*.cpp")
|
||||
427
core/config/engine.cpp
Normal file
427
core/config/engine.cpp
Normal file
@@ -0,0 +1,427 @@
|
||||
/**************************************************************************/
|
||||
/* engine.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 "engine.h"
|
||||
|
||||
#include "core/authors.gen.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/donors.gen.h"
|
||||
#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.");
|
||||
ips = p_ips;
|
||||
}
|
||||
|
||||
int Engine::get_physics_ticks_per_second() const {
|
||||
return ips;
|
||||
}
|
||||
|
||||
void Engine::set_max_physics_steps_per_frame(int p_max_physics_steps) {
|
||||
ERR_FAIL_COND_MSG(p_max_physics_steps <= 0, "Maximum number of physics steps per frame must be greater than 0.");
|
||||
max_physics_steps_per_frame = p_max_physics_steps;
|
||||
}
|
||||
|
||||
int Engine::get_max_physics_steps_per_frame() const {
|
||||
return max_physics_steps_per_frame;
|
||||
}
|
||||
|
||||
void Engine::set_physics_jitter_fix(double p_threshold) {
|
||||
if (p_threshold < 0) {
|
||||
p_threshold = 0;
|
||||
}
|
||||
physics_jitter_fix = p_threshold;
|
||||
}
|
||||
|
||||
double Engine::get_physics_jitter_fix() const {
|
||||
return physics_jitter_fix;
|
||||
}
|
||||
|
||||
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 {
|
||||
return _max_fps;
|
||||
}
|
||||
|
||||
void Engine::set_audio_output_latency(int p_msec) {
|
||||
_audio_output_latency = p_msec > 1 ? p_msec : 1;
|
||||
}
|
||||
|
||||
int Engine::get_audio_output_latency() const {
|
||||
return _audio_output_latency;
|
||||
}
|
||||
|
||||
void Engine::increment_frames_drawn() {
|
||||
if (frame_server_synced) {
|
||||
server_syncs++;
|
||||
} else {
|
||||
server_syncs = 0;
|
||||
}
|
||||
frame_server_synced = false;
|
||||
|
||||
frames_drawn++;
|
||||
}
|
||||
|
||||
uint64_t Engine::get_frames_drawn() {
|
||||
return frames_drawn;
|
||||
}
|
||||
|
||||
void Engine::set_frame_delay(uint32_t p_msec) {
|
||||
_frame_delay = p_msec;
|
||||
}
|
||||
|
||||
uint32_t Engine::get_frame_delay() const {
|
||||
return _frame_delay;
|
||||
}
|
||||
|
||||
void Engine::set_time_scale(double p_scale) {
|
||||
_time_scale = 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;
|
||||
}
|
||||
|
||||
Dictionary Engine::get_version_info() const {
|
||||
Dictionary dict;
|
||||
dict["major"] = GODOT_VERSION_MAJOR;
|
||||
dict["minor"] = GODOT_VERSION_MINOR;
|
||||
dict["patch"] = GODOT_VERSION_PATCH;
|
||||
dict["hex"] = GODOT_VERSION_HEX;
|
||||
dict["status"] = GODOT_VERSION_STATUS;
|
||||
dict["build"] = GODOT_VERSION_BUILD;
|
||||
|
||||
String hash = String(GODOT_VERSION_HASH);
|
||||
dict["hash"] = hash.is_empty() ? String("unknown") : hash;
|
||||
|
||||
dict["timestamp"] = GODOT_VERSION_TIMESTAMP;
|
||||
|
||||
String stringver = String(dict["major"]) + "." + String(dict["minor"]);
|
||||
if ((int)dict["patch"] != 0) {
|
||||
stringver += "." + String(dict["patch"]);
|
||||
}
|
||||
stringver += "-" + String(dict["status"]) + " (" + String(dict["build"]) + ")";
|
||||
dict["string"] = stringver;
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
static Array array_from_info(const char *const *info_list) {
|
||||
Array arr;
|
||||
for (int i = 0; info_list[i] != nullptr; i++) {
|
||||
arr.push_back(String::utf8(info_list[i]));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
static Array array_from_info_count(const char *const *info_list, int info_count) {
|
||||
Array arr;
|
||||
for (int i = 0; i < info_count; i++) {
|
||||
arr.push_back(String::utf8(info_list[i]));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
Dictionary Engine::get_author_info() const {
|
||||
Dictionary dict;
|
||||
|
||||
dict["lead_developers"] = array_from_info(AUTHORS_LEAD_DEVELOPERS);
|
||||
dict["project_managers"] = array_from_info(AUTHORS_PROJECT_MANAGERS);
|
||||
dict["founders"] = array_from_info(AUTHORS_FOUNDERS);
|
||||
dict["developers"] = array_from_info(AUTHORS_DEVELOPERS);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
TypedArray<Dictionary> Engine::get_copyright_info() const {
|
||||
TypedArray<Dictionary> components;
|
||||
for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
|
||||
const ComponentCopyright &cp_info = COPYRIGHT_INFO[component_index];
|
||||
Dictionary component_dict;
|
||||
component_dict["name"] = String::utf8(cp_info.name);
|
||||
Array parts;
|
||||
for (int i = 0; i < cp_info.part_count; i++) {
|
||||
const ComponentCopyrightPart &cp_part = cp_info.parts[i];
|
||||
Dictionary part_dict;
|
||||
part_dict["files"] = array_from_info_count(cp_part.files, cp_part.file_count);
|
||||
part_dict["copyright"] = array_from_info_count(cp_part.copyright_statements, cp_part.copyright_count);
|
||||
part_dict["license"] = String::utf8(cp_part.license);
|
||||
parts.push_back(part_dict);
|
||||
}
|
||||
component_dict["parts"] = parts;
|
||||
|
||||
components.push_back(component_dict);
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
Dictionary Engine::get_donor_info() const {
|
||||
Dictionary donors;
|
||||
donors["patrons"] = array_from_info(DONORS_PATRONS);
|
||||
donors["platinum_sponsors"] = array_from_info(DONORS_SPONSORS_PLATINUM);
|
||||
donors["gold_sponsors"] = array_from_info(DONORS_SPONSORS_GOLD);
|
||||
donors["silver_sponsors"] = array_from_info(DONORS_SPONSORS_SILVER);
|
||||
donors["diamond_members"] = array_from_info(DONORS_MEMBERS_DIAMOND);
|
||||
donors["titanium_members"] = array_from_info(DONORS_MEMBERS_TITANIUM);
|
||||
donors["platinum_members"] = array_from_info(DONORS_MEMBERS_PLATINUM);
|
||||
donors["gold_members"] = array_from_info(DONORS_MEMBERS_GOLD);
|
||||
return donors;
|
||||
}
|
||||
|
||||
Dictionary Engine::get_license_info() const {
|
||||
Dictionary licenses;
|
||||
for (int i = 0; i < LICENSE_COUNT; i++) {
|
||||
licenses[LICENSE_NAMES[i]] = LICENSE_BODIES[i];
|
||||
}
|
||||
return licenses;
|
||||
}
|
||||
|
||||
String Engine::get_license_text() const {
|
||||
return String(GODOT_LICENSE_TEXT);
|
||||
}
|
||||
|
||||
String Engine::get_architecture_name() const {
|
||||
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
|
||||
return "x86_64";
|
||||
#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)
|
||||
return "x86_32";
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
|
||||
return "arm64";
|
||||
#elif defined(__arm__) || defined(_M_ARM)
|
||||
return "arm32";
|
||||
#elif defined(__riscv)
|
||||
return "rv64";
|
||||
#elif defined(__powerpc64__)
|
||||
return "ppc64";
|
||||
#elif defined(__loongarch64)
|
||||
return "loongarch64";
|
||||
#elif defined(__wasm64__)
|
||||
return "wasm64";
|
||||
#elif defined(__wasm32__)
|
||||
return "wasm32";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Engine::is_abort_on_gpu_errors_enabled() const {
|
||||
return abort_on_gpu_errors;
|
||||
}
|
||||
|
||||
int32_t Engine::get_gpu_index() const {
|
||||
return gpu_idx;
|
||||
}
|
||||
|
||||
bool Engine::is_validation_layers_enabled() const {
|
||||
return use_validation_layers;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool Engine::is_printing_error_messages() const {
|
||||
return CoreGlobals::print_error_enabled;
|
||||
}
|
||||
|
||||
void Engine::print_header(const String &p_string) const {
|
||||
if (_print_header) {
|
||||
print_line(p_string);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::print_header_rich(const String &p_string) const {
|
||||
if (_print_header) {
|
||||
print_line_rich(p_string);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::add_singleton(const Singleton &p_singleton) {
|
||||
ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), vformat("Can't register singleton '%s' because it already exists.", p_singleton.name));
|
||||
singletons.push_back(p_singleton);
|
||||
singleton_ptrs[p_singleton.name] = p_singleton.ptr;
|
||||
}
|
||||
|
||||
Object *Engine::get_singleton_object(const StringName &p_name) const {
|
||||
HashMap<StringName, Object *>::ConstIterator E = singleton_ptrs.find(p_name);
|
||||
ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("Failed to retrieve non-existent singleton '%s'.", p_name));
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (!is_editor_hint() && is_singleton_editor_only(p_name)) {
|
||||
ERR_FAIL_V_MSG(nullptr, vformat("Can't retrieve singleton '%s' outside of editor.", p_name));
|
||||
}
|
||||
#endif
|
||||
|
||||
return E->value;
|
||||
}
|
||||
|
||||
bool Engine::is_singleton_user_created(const StringName &p_name) const {
|
||||
ERR_FAIL_COND_V(!singleton_ptrs.has(p_name), false);
|
||||
|
||||
for (const Singleton &E : singletons) {
|
||||
if (E.name == p_name && E.user_created) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Engine::is_singleton_editor_only(const StringName &p_name) const {
|
||||
ERR_FAIL_COND_V(!singleton_ptrs.has(p_name), false);
|
||||
|
||||
for (const Singleton &E : singletons) {
|
||||
if (E.name == p_name && E.editor_only) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Engine::remove_singleton(const StringName &p_name) {
|
||||
ERR_FAIL_COND(!singleton_ptrs.has(p_name));
|
||||
|
||||
for (List<Singleton>::Element *E = singletons.front(); E; E = E->next()) {
|
||||
if (E->get().name == p_name) {
|
||||
singletons.erase(E);
|
||||
singleton_ptrs.erase(p_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::has_singleton(const StringName &p_name) const {
|
||||
return singleton_ptrs.has(p_name);
|
||||
}
|
||||
|
||||
void Engine::get_singletons(List<Singleton> *p_singletons) {
|
||||
for (const Singleton &E : singletons) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (!is_editor_hint() && E.editor_only) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
p_singletons->push_back(E);
|
||||
}
|
||||
}
|
||||
|
||||
String Engine::get_write_movie_path() const {
|
||||
return write_movie_path;
|
||||
}
|
||||
|
||||
void Engine::set_write_movie_path(const String &p_path) {
|
||||
write_movie_path = p_path;
|
||||
}
|
||||
|
||||
void Engine::set_shader_cache_path(const String &p_path) {
|
||||
shader_cache_path = p_path;
|
||||
}
|
||||
String Engine::get_shader_cache_path() const {
|
||||
return shader_cache_path;
|
||||
}
|
||||
|
||||
Engine *Engine::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
bool Engine::notify_frame_server_synced() {
|
||||
frame_server_synced = true;
|
||||
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;
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
if (singleton == this) {
|
||||
singleton = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
|
||||
name(p_name),
|
||||
ptr(p_ptr),
|
||||
class_name(p_class_name) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
RefCounted *rc = Object::cast_to<RefCounted>(p_ptr);
|
||||
if (rc && !rc->is_referenced()) {
|
||||
WARN_PRINT("You must use Ref<> to ensure the lifetime of a RefCounted object intended to be used as a singleton.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
215
core/config/engine.h
Normal file
215
core/config/engine.h
Normal file
@@ -0,0 +1,215 @@
|
||||
/**************************************************************************/
|
||||
/* engine.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/main_loop.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
struct Singleton {
|
||||
StringName name;
|
||||
Object *ptr = nullptr;
|
||||
StringName class_name; // Used for binding generation hinting.
|
||||
// Singleton scope flags.
|
||||
bool user_created = false;
|
||||
bool editor_only = false;
|
||||
|
||||
Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName());
|
||||
};
|
||||
|
||||
private:
|
||||
friend class Main;
|
||||
|
||||
uint64_t frames_drawn = 0;
|
||||
uint32_t _frame_delay = 0;
|
||||
uint64_t _frame_ticks = 0;
|
||||
double _process_step = 0;
|
||||
|
||||
int ips = 60;
|
||||
double physics_jitter_fix = 0.5;
|
||||
double _fps = 1;
|
||||
int _max_fps = 0;
|
||||
int _audio_output_latency = 0;
|
||||
double _time_scale = 1.0;
|
||||
uint64_t _physics_frames = 0;
|
||||
int max_physics_steps_per_frame = 8;
|
||||
double _physics_interpolation_fraction = 0.0f;
|
||||
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;
|
||||
bool _in_physics = false;
|
||||
|
||||
List<Singleton> singletons;
|
||||
HashMap<StringName, Object *> singleton_ptrs;
|
||||
|
||||
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;
|
||||
|
||||
static inline Engine *singleton = nullptr;
|
||||
|
||||
String write_movie_path;
|
||||
String shader_cache_path;
|
||||
|
||||
static constexpr int SERVER_SYNC_FRAME_COUNT_WARNING = 5;
|
||||
int server_syncs = 0;
|
||||
bool frame_server_synced = false;
|
||||
|
||||
bool freeze_time_scale = false;
|
||||
|
||||
public:
|
||||
static Engine *get_singleton();
|
||||
|
||||
virtual void set_physics_ticks_per_second(int p_ips);
|
||||
virtual int get_physics_ticks_per_second() const;
|
||||
|
||||
virtual void set_max_physics_steps_per_frame(int p_max_physics_steps);
|
||||
virtual int get_max_physics_steps_per_frame() const;
|
||||
|
||||
void set_physics_jitter_fix(double p_threshold);
|
||||
double get_physics_jitter_fix() const;
|
||||
|
||||
virtual void set_max_fps(int p_fps);
|
||||
virtual int get_max_fps() const;
|
||||
|
||||
virtual void set_audio_output_latency(int p_msec);
|
||||
virtual int get_audio_output_latency() const;
|
||||
|
||||
virtual double get_frames_per_second() const { return _fps; }
|
||||
|
||||
uint64_t get_frames_drawn();
|
||||
|
||||
uint64_t get_physics_frames() const { return _physics_frames; }
|
||||
uint64_t get_process_frames() const { return _process_frames; }
|
||||
bool is_in_physics_frame() const { return _in_physics; }
|
||||
uint64_t get_frame_ticks() const { return _frame_ticks; }
|
||||
double get_process_step() const { return _process_step; }
|
||||
double get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; }
|
||||
|
||||
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;
|
||||
void print_header(const String &p_string) const;
|
||||
void print_header_rich(const String &p_string) const;
|
||||
|
||||
void set_frame_delay(uint32_t p_msec);
|
||||
uint32_t get_frame_delay() const;
|
||||
|
||||
void add_singleton(const Singleton &p_singleton);
|
||||
void get_singletons(List<Singleton> *p_singletons);
|
||||
bool has_singleton(const StringName &p_name) const;
|
||||
Object *get_singleton_object(const StringName &p_name) const;
|
||||
void remove_singleton(const StringName &p_name);
|
||||
bool is_singleton_user_created(const StringName &p_name) const;
|
||||
bool is_singleton_editor_only(const StringName &p_name) const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; }
|
||||
_FORCE_INLINE_ bool is_editor_hint() const { return editor_hint; }
|
||||
|
||||
_FORCE_INLINE_ void set_project_manager_hint(bool p_enabled) { project_manager_hint = p_enabled; }
|
||||
_FORCE_INLINE_ bool is_project_manager_hint() const { return project_manager_hint; }
|
||||
|
||||
_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; }
|
||||
|
||||
_FORCE_INLINE_ void set_project_manager_hint(bool p_enabled) {}
|
||||
_FORCE_INLINE_ bool is_project_manager_hint() const { return false; }
|
||||
|
||||
_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;
|
||||
Dictionary get_author_info() const;
|
||||
TypedArray<Dictionary> get_copyright_info() const;
|
||||
Dictionary get_donor_info() const;
|
||||
Dictionary get_license_info() const;
|
||||
String get_license_text() const;
|
||||
|
||||
void set_write_movie_path(const String &p_path);
|
||||
String get_write_movie_path() const;
|
||||
|
||||
String get_architecture_name() const;
|
||||
|
||||
void set_shader_cache_path(const String &p_path);
|
||||
String get_shader_cache_path() const;
|
||||
|
||||
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();
|
||||
};
|
||||
1750
core/config/project_settings.cpp
Normal file
1750
core/config/project_settings.cpp
Normal file
File diff suppressed because it is too large
Load Diff
284
core/config/project_settings.h
Normal file
284
core/config/project_settings.h
Normal file
@@ -0,0 +1,284 @@
|
||||
/**************************************************************************/
|
||||
/* project_settings.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
class ProjectSettings : public Object {
|
||||
GDCLASS(ProjectSettings, Object);
|
||||
_THREAD_SAFE_CLASS_
|
||||
friend class TestProjectSettingsInternalsAccessor;
|
||||
|
||||
bool is_changed = false;
|
||||
|
||||
// Starting version from 1 ensures that all callers can reset their tested version to 0,
|
||||
// and will always detect the initial project settings as a "change".
|
||||
uint32_t _version = 1;
|
||||
|
||||
public:
|
||||
typedef HashMap<String, Variant> CustomMap;
|
||||
static inline const String PROJECT_DATA_DIR_NAME_SUFFIX = "godot";
|
||||
static inline const String EDITOR_SETTING_OVERRIDE_PREFIX = "editor_overrides/";
|
||||
|
||||
// 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();
|
||||
const static PackedStringArray get_unsupported_features(const PackedStringArray &p_project_features);
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
struct AutoloadInfo {
|
||||
StringName name;
|
||||
String path;
|
||||
bool is_singleton = false;
|
||||
};
|
||||
|
||||
protected:
|
||||
struct VariantContainer {
|
||||
int order = 0;
|
||||
bool persist = false;
|
||||
bool basic = false;
|
||||
bool internal = false;
|
||||
Variant variant;
|
||||
Variant initial;
|
||||
bool hide_from_editor = false;
|
||||
bool restart_if_changed = false;
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool ignore_value_in_docs = false;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
VariantContainer() {}
|
||||
|
||||
VariantContainer(const Variant &p_variant, int p_order, bool p_persist = false) :
|
||||
order(p_order),
|
||||
persist(p_persist),
|
||||
variant(p_variant) {
|
||||
}
|
||||
};
|
||||
|
||||
int last_order = NO_BUILTIN_ORDER_BASE;
|
||||
int last_builtin_order = 0;
|
||||
uint64_t last_save_time = 0;
|
||||
|
||||
RBMap<StringName, VariantContainer> props; // NOTE: Key order is used e.g. in the save_custom method.
|
||||
String resource_path;
|
||||
HashMap<StringName, PropertyInfo> custom_prop_info;
|
||||
bool using_datapack = false;
|
||||
bool project_loaded = false;
|
||||
List<String> input_presets;
|
||||
|
||||
HashSet<String> custom_features;
|
||||
HashMap<StringName, LocalVector<Pair<StringName, StringName>>> feature_overrides;
|
||||
|
||||
LocalVector<String> hidden_prefixes;
|
||||
HashMap<StringName, AutoloadInfo> autoloads;
|
||||
HashMap<StringName, String> global_groups;
|
||||
HashMap<StringName, HashSet<StringName>> scene_groups_cache;
|
||||
|
||||
Array global_class_list;
|
||||
bool is_global_class_list_loaded = false;
|
||||
|
||||
String project_data_dir_name;
|
||||
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
bool _property_can_revert(const StringName &p_name) const;
|
||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
||||
|
||||
void _queue_changed();
|
||||
void _emit_changed();
|
||||
|
||||
static inline ProjectSettings *singleton = nullptr;
|
||||
|
||||
Error _load_settings_text(const String &p_path);
|
||||
Error _load_settings_binary(const String &p_path);
|
||||
Error _load_settings_text_or_binary(const String &p_text_path, const String &p_bin_path);
|
||||
|
||||
Error _save_settings_text(const String &p_file, const RBMap<String, List<String>> &props, const CustomMap &p_custom = CustomMap(), const String &p_custom_features = String());
|
||||
Error _save_settings_binary(const String &p_file, const RBMap<String, List<String>> &props, const CustomMap &p_custom = CustomMap(), const String &p_custom_features = String());
|
||||
|
||||
Error _save_custom_bnd(const String &p_file);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
const static PackedStringArray _get_supported_features();
|
||||
const static PackedStringArray _trim_to_supported_features(const PackedStringArray &p_project_features);
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void _convert_to_last_version(int p_from_version);
|
||||
|
||||
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);
|
||||
|
||||
Error _setup(const String &p_path, const String &p_main_pack, bool p_upwards = false, bool p_ignore_override = false);
|
||||
|
||||
void _add_builtin_input_map();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static const int CONFIG_VERSION = 5;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
HashMap<String, PropertyInfo> editor_settings_info;
|
||||
#endif
|
||||
|
||||
void set_setting(const String &p_setting, const Variant &p_value);
|
||||
Variant get_setting(const String &p_setting, const Variant &p_default_value = Variant()) const;
|
||||
TypedArray<Dictionary> get_global_class_list();
|
||||
void refresh_global_class_list();
|
||||
void store_global_class_list(const Array &p_classes);
|
||||
String get_global_class_list_path() const;
|
||||
|
||||
bool has_setting(const String &p_var) const;
|
||||
String localize_path(const String &p_path) const;
|
||||
String globalize_path(const String &p_path) const;
|
||||
|
||||
void set_initial_value(const String &p_name, const Variant &p_value);
|
||||
void set_as_basic(const String &p_name, bool p_basic);
|
||||
void set_as_internal(const String &p_name, bool p_internal);
|
||||
void set_restart_if_changed(const String &p_name, bool p_restart);
|
||||
void set_ignore_value_in_docs(const String &p_name, bool p_ignore);
|
||||
bool get_ignore_value_in_docs(const String &p_name) const;
|
||||
void add_hidden_prefix(const String &p_prefix);
|
||||
|
||||
String get_project_data_dir_name() const;
|
||||
String get_project_data_path() const;
|
||||
String get_resource_path() const;
|
||||
String get_imported_files_path() const;
|
||||
|
||||
static ProjectSettings *get_singleton();
|
||||
|
||||
void clear(const String &p_name);
|
||||
int get_order(const String &p_name) const;
|
||||
void set_order(const String &p_name, int p_order);
|
||||
void set_builtin_order(const String &p_name);
|
||||
bool is_builtin_setting(const String &p_name) const;
|
||||
|
||||
Error setup(const String &p_path, const String &p_main_pack, bool p_upwards = false, bool p_ignore_override = false);
|
||||
|
||||
Error load_custom(const String &p_path);
|
||||
Error save_custom(const String &p_path = "", const CustomMap &p_custom = CustomMap(), const Vector<String> &p_custom_features = Vector<String>(), bool p_merge_with_current = true);
|
||||
Error save();
|
||||
void set_custom_property_info(const PropertyInfo &p_info);
|
||||
const HashMap<StringName, PropertyInfo> &get_custom_property_info() const;
|
||||
uint64_t get_last_saved_time() { return last_save_time; }
|
||||
|
||||
List<String> get_input_presets() const { return input_presets; }
|
||||
|
||||
Variant get_setting_with_override(const StringName &p_name) const;
|
||||
Variant get_setting_with_override_and_custom_features(const StringName &p_name, const Vector<String> &p_features) const;
|
||||
|
||||
bool is_using_datapack() const;
|
||||
bool is_project_loaded() const;
|
||||
|
||||
bool has_custom_feature(const String &p_feature) const;
|
||||
|
||||
const HashMap<StringName, AutoloadInfo> &get_autoload_list() const;
|
||||
void add_autoload(const AutoloadInfo &p_autoload);
|
||||
void remove_autoload(const StringName &p_autoload);
|
||||
bool has_autoload(const StringName &p_autoload) const;
|
||||
AutoloadInfo get_autoload(const StringName &p_name) const;
|
||||
|
||||
const HashMap<StringName, String> &get_global_groups_list() const;
|
||||
void add_global_group(const StringName &p_name, const String &p_description);
|
||||
void remove_global_group(const StringName &p_name);
|
||||
bool has_global_group(const StringName &p_name) const;
|
||||
|
||||
const HashMap<StringName, HashSet<StringName>> &get_scene_groups_cache() const;
|
||||
void add_scene_groups_cache(const StringName &p_path, const HashSet<StringName> &p_cache);
|
||||
void remove_scene_groups_cache(const StringName &p_path);
|
||||
void save_scene_groups_cache();
|
||||
String get_scene_groups_cache_path() const;
|
||||
void load_scene_groups_cache();
|
||||
|
||||
// Testing a version allows fast cached GET_GLOBAL macros.
|
||||
uint32_t get_version() const { return _version; }
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
void set_editor_setting_override(const String &p_setting, const Variant &p_value);
|
||||
bool has_editor_setting_override(const String &p_setting) const;
|
||||
Variant get_editor_setting_override(const String &p_setting) const;
|
||||
|
||||
ProjectSettings();
|
||||
ProjectSettings(const String &p_path);
|
||||
~ProjectSettings();
|
||||
};
|
||||
|
||||
// Not a macro any longer.
|
||||
Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false, bool p_basic = false, bool p_internal = false);
|
||||
Variant _GLOBAL_DEF(const PropertyInfo &p_info, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false, bool p_basic = false, bool p_internal = false);
|
||||
|
||||
#define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value)
|
||||
#define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true)
|
||||
#define GLOBAL_DEF_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true)
|
||||
#define GLOBAL_DEF_RST_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true)
|
||||
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get_setting_with_override(m_var)
|
||||
|
||||
#define GLOBAL_DEF_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, false, true)
|
||||
#define GLOBAL_DEF_RST_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, false, true)
|
||||
#define GLOBAL_DEF_NOVAL_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true, true)
|
||||
#define GLOBAL_DEF_RST_NOVAL_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true, true)
|
||||
|
||||
#define GLOBAL_DEF_INTERNAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, false, false, true)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Cached versions of GLOBAL_GET.
|
||||
// Cached but uses a typed variable for storage, this can be more efficient.
|
||||
// Variables prefixed with _ggc_ to avoid shadowing warnings.
|
||||
#define GLOBAL_GET_CACHED(m_type, m_setting_name) ([](const char *p_name) -> m_type {\
|
||||
static_assert(std::is_trivially_destructible<m_type>::value, "GLOBAL_GET_CACHED must use a trivial type that allows static lifetime.");\
|
||||
static m_type _ggc_local_var;\
|
||||
static uint32_t _ggc_local_version = 0;\
|
||||
static SpinLock _ggc_spin;\
|
||||
uint32_t _ggc_new_version = ProjectSettings::get_singleton()->get_version();\
|
||||
if (_ggc_local_version != _ggc_new_version) {\
|
||||
_ggc_spin.lock();\
|
||||
_ggc_local_version = _ggc_new_version;\
|
||||
_ggc_local_var = ProjectSettings::get_singleton()->get_setting_with_override(p_name);\
|
||||
m_type _ggc_temp = _ggc_local_var;\
|
||||
_ggc_spin.unlock();\
|
||||
return _ggc_temp;\
|
||||
}\
|
||||
_ggc_spin.lock();\
|
||||
m_type _ggc_temp2 = _ggc_local_var;\
|
||||
_ggc_spin.unlock();\
|
||||
return _ggc_temp2; })(m_setting_name)
|
||||
64
core/core_bind.compat.inc
Normal file
64
core/core_bind.compat.inc
Normal file
@@ -0,0 +1,64 @@
|
||||
/**************************************************************************/
|
||||
/* core_bind.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
|
||||
|
||||
namespace CoreBind {
|
||||
|
||||
// Semaphore
|
||||
|
||||
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", "buffer_size"), &OS::read_string_from_stdin);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("read_buffer_from_stdin", "buffer_size"), &OS::read_buffer_from_stdin);
|
||||
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 CoreBind
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
2321
core/core_bind.cpp
Normal file
2321
core/core_bind.cpp
Normal file
File diff suppressed because it is too large
Load Diff
713
core/core_bind.h
Normal file
713
core/core_bind.h
Normal file
@@ -0,0 +1,713 @@
|
||||
/**************************************************************************/
|
||||
/* core_bind.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/debugger/engine_profiler.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/object/script_backtrace.h"
|
||||
#include "core/os/semaphore.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
class MainLoop;
|
||||
|
||||
namespace CoreBind {
|
||||
|
||||
class ResourceLoader : public Object {
|
||||
GDCLASS(ResourceLoader, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static inline ResourceLoader *singleton = nullptr;
|
||||
|
||||
public:
|
||||
enum ThreadLoadStatus {
|
||||
THREAD_LOAD_INVALID_RESOURCE,
|
||||
THREAD_LOAD_IN_PROGRESS,
|
||||
THREAD_LOAD_FAILED,
|
||||
THREAD_LOAD_LOADED
|
||||
};
|
||||
|
||||
enum CacheMode {
|
||||
CACHE_MODE_IGNORE,
|
||||
CACHE_MODE_REUSE,
|
||||
CACHE_MODE_REPLACE,
|
||||
CACHE_MODE_IGNORE_DEEP,
|
||||
CACHE_MODE_REPLACE_DEEP,
|
||||
};
|
||||
|
||||
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 = 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);
|
||||
Vector<String> get_recognized_extensions_for_type(const String &p_type);
|
||||
void add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front);
|
||||
void remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader);
|
||||
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; }
|
||||
};
|
||||
|
||||
class ResourceSaver : public Object {
|
||||
GDCLASS(ResourceSaver, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static inline ResourceSaver *singleton = nullptr;
|
||||
|
||||
public:
|
||||
enum SaverFlags {
|
||||
FLAG_NONE = 0,
|
||||
FLAG_RELATIVE_PATHS = 1,
|
||||
FLAG_BUNDLE_RESOURCES = 2,
|
||||
FLAG_CHANGE_PATH = 4,
|
||||
FLAG_OMIT_EDITOR_PROPERTIES = 8,
|
||||
FLAG_SAVE_BIG_ENDIAN = 16,
|
||||
FLAG_COMPRESS = 32,
|
||||
FLAG_REPLACE_SUBRESOURCE_PATHS = 64,
|
||||
};
|
||||
|
||||
static ResourceSaver *get_singleton() { return singleton; }
|
||||
|
||||
Error save(const Ref<Resource> &p_resource, const String &p_path, BitField<SaverFlags> p_flags);
|
||||
Error set_uid(const String &p_path, ResourceUID::ID p_uid);
|
||||
Vector<String> get_recognized_extensions(const Ref<Resource> &p_resource);
|
||||
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; }
|
||||
};
|
||||
|
||||
class Logger : public RefCounted {
|
||||
GDCLASS(Logger, RefCounted);
|
||||
|
||||
public:
|
||||
enum ErrorType {
|
||||
ERROR_TYPE_ERROR,
|
||||
ERROR_TYPE_WARNING,
|
||||
ERROR_TYPE_SCRIPT,
|
||||
ERROR_TYPE_SHADER,
|
||||
};
|
||||
|
||||
protected:
|
||||
GDVIRTUAL2(_log_message, String, bool);
|
||||
GDVIRTUAL8(_log_error, String, String, int, String, String, bool, int, TypedArray<ScriptBacktrace>);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERROR_TYPE_ERROR, const TypedArray<ScriptBacktrace> &p_script_backtraces = {});
|
||||
virtual void log_message(const String &p_text, bool p_error);
|
||||
};
|
||||
|
||||
class OS : public Object {
|
||||
GDCLASS(OS, Object);
|
||||
|
||||
mutable HashMap<String, bool> feature_cache;
|
||||
|
||||
class LoggerBind : public ::Logger {
|
||||
public:
|
||||
LocalVector<Ref<CoreBind::Logger>> loggers;
|
||||
|
||||
virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
|
||||
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces = {}) override;
|
||||
|
||||
void clear() { loggers.clear(); }
|
||||
};
|
||||
|
||||
LoggerBind *logger_bind = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static inline OS *singleton = nullptr;
|
||||
|
||||
#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();
|
||||
|
||||
void set_low_processor_usage_mode(bool p_enabled);
|
||||
bool is_in_low_processor_usage_mode() const;
|
||||
|
||||
void set_low_processor_usage_mode_sleep_usec(int p_usec);
|
||||
int get_low_processor_usage_mode_sleep_usec() const;
|
||||
|
||||
void set_delta_smoothing(bool p_enabled);
|
||||
bool is_delta_smoothing_enabled() const;
|
||||
|
||||
void alert(const String &p_alert, const String &p_title = "ALERT!");
|
||||
void crash(const String &p_message);
|
||||
|
||||
Vector<String> get_system_fonts() const;
|
||||
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(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 open_with_program(const String &p_program_path, const Vector<String> &p_paths);
|
||||
Error kill(int p_pid);
|
||||
Error shell_open(const String &p_uri);
|
||||
Error shell_show_in_file_manager(const String &p_path, bool p_open_folder = true);
|
||||
|
||||
bool is_process_running(int p_pid) const;
|
||||
int get_process_exit_code(int p_pid) const;
|
||||
int get_process_id() const;
|
||||
|
||||
void set_restart_on_exit(bool p_restart, const Vector<String> &p_restart_arguments = Vector<String>());
|
||||
bool is_restart_on_exit_set() const;
|
||||
Vector<String> get_restart_on_exit_arguments() const;
|
||||
|
||||
bool has_environment(const String &p_var) const;
|
||||
String get_environment(const String &p_var) const;
|
||||
void set_environment(const String &p_var, const String &p_value) const;
|
||||
void unset_environment(const String &p_var) const;
|
||||
|
||||
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();
|
||||
|
||||
Vector<String> get_video_adapter_driver_info() const;
|
||||
|
||||
String get_locale() const;
|
||||
String get_locale_language() const;
|
||||
|
||||
String get_model_name() const;
|
||||
|
||||
bool is_debug_build() const;
|
||||
|
||||
String get_unique_id() const;
|
||||
|
||||
String get_keycode_string(Key p_code) const;
|
||||
bool is_keycode_unicode(char32_t p_unicode) const;
|
||||
Key find_keycode_from_string(const String &p_code) const;
|
||||
|
||||
void set_use_file_access_save_and_swap(bool p_enable);
|
||||
|
||||
uint64_t get_static_memory_usage() const;
|
||||
uint64_t get_static_memory_peak_usage() const;
|
||||
Dictionary get_memory_info() const;
|
||||
|
||||
void delay_usec(int p_usec) const;
|
||||
void delay_msec(int p_msec) const;
|
||||
uint64_t get_ticks_msec() const;
|
||||
uint64_t get_ticks_usec() const;
|
||||
|
||||
bool is_userfs_persistent() const;
|
||||
|
||||
bool is_stdout_verbose() const;
|
||||
|
||||
int get_processor_count() const;
|
||||
String get_processor_name() const;
|
||||
|
||||
enum SystemDir {
|
||||
SYSTEM_DIR_DESKTOP,
|
||||
SYSTEM_DIR_DCIM,
|
||||
SYSTEM_DIR_DOCUMENTS,
|
||||
SYSTEM_DIR_DOWNLOADS,
|
||||
SYSTEM_DIR_MOVIES,
|
||||
SYSTEM_DIR_MUSIC,
|
||||
SYSTEM_DIR_PICTURES,
|
||||
SYSTEM_DIR_RINGTONES,
|
||||
};
|
||||
|
||||
String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
|
||||
|
||||
Error move_to_trash(const String &p_path) const;
|
||||
String get_user_data_dir() const;
|
||||
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;
|
||||
::Thread::ID get_main_thread_id() const;
|
||||
|
||||
bool has_feature(const String &p_feature) const;
|
||||
bool is_sandboxed() const;
|
||||
|
||||
bool request_permission(const String &p_name);
|
||||
bool request_permissions();
|
||||
Vector<String> get_granted_permissions() const;
|
||||
void revoke_granted_permissions();
|
||||
|
||||
void add_logger(const Ref<Logger> &p_logger);
|
||||
void remove_logger(const Ref<Logger> &p_logger);
|
||||
void remove_script_loggers(const ScriptLanguage *p_script);
|
||||
|
||||
static OS *get_singleton() { return singleton; }
|
||||
|
||||
OS();
|
||||
~OS();
|
||||
};
|
||||
|
||||
class Geometry2D : public Object {
|
||||
GDCLASS(Geometry2D, Object);
|
||||
|
||||
static inline Geometry2D *singleton = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static Geometry2D *get_singleton();
|
||||
Variant segment_intersects_segment(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b);
|
||||
Variant line_intersects_line(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b);
|
||||
Vector<Vector2> get_closest_points_between_segments(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2);
|
||||
Vector2 get_closest_point_to_segment(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b);
|
||||
Vector2 get_closest_point_to_segment_uncapped(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b);
|
||||
bool point_is_inside_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) const;
|
||||
|
||||
bool is_point_in_circle(const Vector2 &p_point, const Vector2 &p_circle_pos, real_t p_circle_radius);
|
||||
real_t segment_intersects_circle(const Vector2 &p_from, const Vector2 &p_to, const Vector2 &p_circle_pos, real_t p_circle_radius);
|
||||
|
||||
bool is_polygon_clockwise(const Vector<Vector2> &p_polygon);
|
||||
bool is_point_in_polygon(const Point2 &p_point, const Vector<Vector2> &p_polygon);
|
||||
Vector<int> triangulate_polygon(const Vector<Vector2> &p_polygon);
|
||||
Vector<int> triangulate_delaunay(const Vector<Vector2> &p_points);
|
||||
Vector<Point2> convex_hull(const Vector<Point2> &p_points);
|
||||
TypedArray<PackedVector2Array> decompose_polygon_in_convex(const Vector<Vector2> &p_polygon);
|
||||
|
||||
enum PolyBooleanOperation {
|
||||
OPERATION_UNION,
|
||||
OPERATION_DIFFERENCE,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_XOR
|
||||
};
|
||||
// 2D polygon boolean operations.
|
||||
TypedArray<PackedVector2Array> merge_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // Union (add).
|
||||
TypedArray<PackedVector2Array> clip_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // Difference (subtract).
|
||||
TypedArray<PackedVector2Array> intersect_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // Common area (multiply).
|
||||
TypedArray<PackedVector2Array> exclude_polygons(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // All but common area (xor).
|
||||
|
||||
// 2D polyline vs polygon operations.
|
||||
TypedArray<PackedVector2Array> clip_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon); // Cut.
|
||||
TypedArray<PackedVector2Array> intersect_polyline_with_polygon(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon); // Chop.
|
||||
|
||||
// 2D offset polygons/polylines.
|
||||
enum PolyJoinType {
|
||||
JOIN_SQUARE,
|
||||
JOIN_ROUND,
|
||||
JOIN_MITER
|
||||
};
|
||||
enum PolyEndType {
|
||||
END_POLYGON,
|
||||
END_JOINED,
|
||||
END_BUTT,
|
||||
END_SQUARE,
|
||||
END_ROUND
|
||||
};
|
||||
TypedArray<PackedVector2Array> offset_polygon(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE);
|
||||
TypedArray<PackedVector2Array> offset_polyline(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE, PolyEndType p_end_type = END_SQUARE);
|
||||
|
||||
Dictionary make_atlas(const Vector<Size2> &p_rects);
|
||||
|
||||
TypedArray<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to);
|
||||
|
||||
Geometry2D() { singleton = this; }
|
||||
};
|
||||
|
||||
class Geometry3D : public Object {
|
||||
GDCLASS(Geometry3D, Object);
|
||||
|
||||
static inline Geometry3D *singleton = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static Geometry3D *get_singleton();
|
||||
Vector<Vector3> compute_convex_mesh_points(const TypedArray<Plane> &p_planes);
|
||||
TypedArray<Plane> build_box_planes(const Vector3 &p_extents);
|
||||
TypedArray<Plane> build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z);
|
||||
TypedArray<Plane> build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z);
|
||||
Vector<Vector3> get_closest_points_between_segments(const Vector3 &p1, const Vector3 &p2, const Vector3 &q1, const Vector3 &q2);
|
||||
Vector3 get_closest_point_to_segment(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b);
|
||||
Vector3 get_closest_point_to_segment_uncapped(const Vector3 &p_point, const Vector3 &p_a, const Vector3 &p_b);
|
||||
Vector3 get_triangle_barycentric_coords(const Vector3 &p_point, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2);
|
||||
Variant ray_intersects_triangle(const Vector3 &p_from, const Vector3 &p_dir, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2);
|
||||
Variant segment_intersects_triangle(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_v0, const Vector3 &p_v1, const Vector3 &p_v2);
|
||||
|
||||
Vector<Vector3> segment_intersects_sphere(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_sphere_pos, real_t p_sphere_radius);
|
||||
Vector<Vector3> segment_intersects_cylinder(const Vector3 &p_from, const Vector3 &p_to, float p_height, float p_radius);
|
||||
Vector<Vector3> segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray<Plane> &p_planes);
|
||||
|
||||
Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane);
|
||||
Vector<int32_t> tetrahedralize_delaunay(const Vector<Vector3> &p_points);
|
||||
|
||||
Geometry3D() { singleton = this; }
|
||||
};
|
||||
|
||||
class Marshalls : public Object {
|
||||
GDCLASS(Marshalls, Object);
|
||||
|
||||
static inline Marshalls *singleton = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static Marshalls *get_singleton();
|
||||
|
||||
String variant_to_base64(const Variant &p_var, bool p_full_objects = false);
|
||||
Variant base64_to_variant(const String &p_str, bool p_allow_objects = false);
|
||||
|
||||
String raw_to_base64(const Vector<uint8_t> &p_arr);
|
||||
Vector<uint8_t> base64_to_raw(const String &p_str);
|
||||
|
||||
String utf8_to_base64(const String &p_str);
|
||||
String base64_to_utf8(const String &p_str);
|
||||
|
||||
Marshalls() { singleton = this; }
|
||||
~Marshalls() { singleton = nullptr; }
|
||||
};
|
||||
|
||||
class Mutex : public RefCounted {
|
||||
GDCLASS(Mutex, RefCounted);
|
||||
::Mutex mutex;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void lock();
|
||||
bool try_lock();
|
||||
void unlock();
|
||||
};
|
||||
|
||||
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(int p_count = 1);
|
||||
};
|
||||
|
||||
class Thread : public RefCounted {
|
||||
GDCLASS(Thread, RefCounted);
|
||||
|
||||
protected:
|
||||
Variant ret;
|
||||
SafeFlag running;
|
||||
Callable target_callable;
|
||||
::Thread thread;
|
||||
static void _bind_methods();
|
||||
static void _start_func(void *ud);
|
||||
|
||||
public:
|
||||
enum Priority {
|
||||
PRIORITY_LOW,
|
||||
PRIORITY_NORMAL,
|
||||
PRIORITY_HIGH,
|
||||
PRIORITY_MAX
|
||||
};
|
||||
|
||||
Error start(const Callable &p_callable, Priority p_priority = PRIORITY_NORMAL);
|
||||
String get_id() const;
|
||||
bool is_started() const;
|
||||
bool is_alive() const;
|
||||
Variant wait_to_finish();
|
||||
|
||||
static void set_thread_safety_checks_enabled(bool p_enabled);
|
||||
};
|
||||
|
||||
namespace Special {
|
||||
|
||||
class ClassDB : public Object {
|
||||
GDCLASS(ClassDB, Object);
|
||||
|
||||
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;
|
||||
bool class_exists(const StringName &p_class) const;
|
||||
bool is_parent_class(const StringName &p_class, const StringName &p_inherits) const;
|
||||
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;
|
||||
|
||||
Variant class_get_property_default_value(const StringName &p_class, const StringName &p_property) const;
|
||||
|
||||
bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
|
||||
|
||||
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;
|
||||
int64_t class_get_integer_constant(const StringName &p_class, const StringName &p_name) const;
|
||||
|
||||
bool class_has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
|
||||
PackedStringArray class_get_enum_list(const StringName &p_class, bool p_no_inheritance = false) const;
|
||||
PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
|
||||
StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
|
||||
|
||||
bool is_class_enum_bitfield(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
|
||||
|
||||
bool is_class_enabled(const StringName &p_class) const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
ClassDB() {}
|
||||
~ClassDB() {}
|
||||
};
|
||||
|
||||
} // namespace Special
|
||||
|
||||
class Engine : public Object {
|
||||
GDCLASS(Engine, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static inline Engine *singleton = nullptr;
|
||||
|
||||
public:
|
||||
static Engine *get_singleton() { return singleton; }
|
||||
void set_physics_ticks_per_second(int p_ips);
|
||||
int get_physics_ticks_per_second() const;
|
||||
|
||||
void set_max_physics_steps_per_frame(int p_max_physics_steps);
|
||||
int get_max_physics_steps_per_frame() const;
|
||||
|
||||
void set_physics_jitter_fix(double p_threshold);
|
||||
double get_physics_jitter_fix() const;
|
||||
double get_physics_interpolation_fraction() const;
|
||||
|
||||
void set_max_fps(int p_fps);
|
||||
int get_max_fps() const;
|
||||
|
||||
double get_frames_per_second() const;
|
||||
uint64_t get_physics_frames() const;
|
||||
uint64_t get_process_frames() const;
|
||||
|
||||
int get_frames_drawn();
|
||||
|
||||
void set_time_scale(double p_scale);
|
||||
double get_time_scale();
|
||||
|
||||
MainLoop *get_main_loop() const;
|
||||
|
||||
Dictionary get_version_info() const;
|
||||
Dictionary get_author_info() const;
|
||||
TypedArray<Dictionary> get_copyright_info() const;
|
||||
Dictionary get_donor_info() const;
|
||||
Dictionary get_license_info() const;
|
||||
String get_license_text() const;
|
||||
|
||||
String get_architecture_name() const;
|
||||
|
||||
bool is_in_physics_frame() const;
|
||||
|
||||
bool has_singleton(const StringName &p_name) const;
|
||||
Object *get_singleton_object(const StringName &p_name) const;
|
||||
void register_singleton(const StringName &p_name, Object *p_object);
|
||||
void unregister_singleton(const StringName &p_name);
|
||||
Vector<String> get_singleton_list() const;
|
||||
|
||||
Error register_script_language(ScriptLanguage *p_language);
|
||||
Error unregister_script_language(const ScriptLanguage *p_language);
|
||||
int get_script_language_count();
|
||||
ScriptLanguage *get_script_language(int p_index) const;
|
||||
TypedArray<ScriptBacktrace> capture_script_backtraces(bool p_include_variables = false) const;
|
||||
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
Engine() { singleton = this; }
|
||||
};
|
||||
|
||||
class EngineDebugger : public Object {
|
||||
GDCLASS(EngineDebugger, Object);
|
||||
|
||||
HashMap<StringName, Callable> captures;
|
||||
HashMap<StringName, Ref<EngineProfiler>> profilers;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static inline EngineDebugger *singleton = nullptr;
|
||||
|
||||
public:
|
||||
static EngineDebugger *get_singleton() { return singleton; }
|
||||
|
||||
bool is_active();
|
||||
|
||||
void register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler);
|
||||
void unregister_profiler(const StringName &p_name);
|
||||
bool is_profiling(const StringName &p_name);
|
||||
bool has_profiler(const StringName &p_name);
|
||||
void profiler_add_frame_data(const StringName &p_name, const Array &p_data);
|
||||
void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
|
||||
|
||||
void register_message_capture(const StringName &p_name, const Callable &p_callable);
|
||||
void unregister_message_capture(const StringName &p_name);
|
||||
bool has_capture(const StringName &p_name);
|
||||
|
||||
void send_message(const String &p_msg, const Array &p_data);
|
||||
void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false);
|
||||
void script_debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
|
||||
|
||||
static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);
|
||||
|
||||
void line_poll();
|
||||
|
||||
void set_lines_left(int p_lines);
|
||||
int get_lines_left() const;
|
||||
|
||||
void set_depth(int p_depth);
|
||||
int get_depth() const;
|
||||
|
||||
bool is_breakpoint(int p_line, const StringName &p_source) const;
|
||||
bool is_skipping_breakpoints() const;
|
||||
void insert_breakpoint(int p_line, const StringName &p_source);
|
||||
void remove_breakpoint(int p_line, const StringName &p_source);
|
||||
void clear_breakpoints();
|
||||
|
||||
EngineDebugger() { singleton = this; }
|
||||
~EngineDebugger();
|
||||
};
|
||||
|
||||
} // namespace CoreBind
|
||||
|
||||
VARIANT_ENUM_CAST(CoreBind::Logger::ErrorType);
|
||||
VARIANT_ENUM_CAST(CoreBind::ResourceLoader::ThreadLoadStatus);
|
||||
VARIANT_ENUM_CAST(CoreBind::ResourceLoader::CacheMode);
|
||||
|
||||
VARIANT_BITFIELD_CAST(CoreBind::ResourceSaver::SaverFlags);
|
||||
|
||||
VARIANT_ENUM_CAST(CoreBind::OS::RenderingDriver);
|
||||
VARIANT_ENUM_CAST(CoreBind::OS::SystemDir);
|
||||
VARIANT_ENUM_CAST(CoreBind::OS::StdHandleType);
|
||||
|
||||
VARIANT_ENUM_CAST(CoreBind::Geometry2D::PolyBooleanOperation);
|
||||
VARIANT_ENUM_CAST(CoreBind::Geometry2D::PolyJoinType);
|
||||
VARIANT_ENUM_CAST(CoreBind::Geometry2D::PolyEndType);
|
||||
|
||||
VARIANT_ENUM_CAST(CoreBind::Thread::Priority);
|
||||
|
||||
VARIANT_ENUM_CAST(CoreBind::Special::ClassDB::APIType);
|
||||
291
core/core_builders.py
Normal file
291
core/core_builders.py
Normal file
@@ -0,0 +1,291 @@
|
||||
"""Functions used to generate source files during build time"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from io import TextIOWrapper
|
||||
|
||||
import methods
|
||||
|
||||
|
||||
# Generate disabled classes
|
||||
def disabled_class_builder(target, source, env):
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
for c in source[0].read():
|
||||
if cs := c.strip():
|
||||
file.write(f"#define ClassDB_Disable_{cs} 1\n")
|
||||
|
||||
|
||||
# Generate version info
|
||||
def version_info_builder(target, source, env):
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(
|
||||
"""\
|
||||
#define GODOT_VERSION_SHORT_NAME "{short_name}"
|
||||
#define GODOT_VERSION_NAME "{name}"
|
||||
#define GODOT_VERSION_MAJOR {major}
|
||||
#define GODOT_VERSION_MINOR {minor}
|
||||
#define GODOT_VERSION_PATCH {patch}
|
||||
#define GODOT_VERSION_STATUS "{status}"
|
||||
#define GODOT_VERSION_BUILD "{build}"
|
||||
#define GODOT_VERSION_MODULE_CONFIG "{module_config}"
|
||||
#define GODOT_VERSION_WEBSITE "{website}"
|
||||
#define GODOT_VERSION_DOCS_BRANCH "{docs_branch}"
|
||||
#define GODOT_VERSION_DOCS_URL "https://docs.godotengine.org/en/" GODOT_VERSION_DOCS_BRANCH
|
||||
""".format(**source[0].read())
|
||||
)
|
||||
|
||||
|
||||
def version_hash_builder(target, source, env):
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(
|
||||
"""\
|
||||
#include "core/version.h"
|
||||
|
||||
const char *const GODOT_VERSION_HASH = "{git_hash}";
|
||||
const uint64_t GODOT_VERSION_TIMESTAMP = {git_timestamp};
|
||||
""".format(**source[0].read())
|
||||
)
|
||||
|
||||
|
||||
def encryption_key_builder(target, source, env):
|
||||
src = source[0].read() or "0" * 64
|
||||
try:
|
||||
buffer = bytes.fromhex(src)
|
||||
if len(buffer) != 32:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
methods.print_error(
|
||||
f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{src}".\n'
|
||||
"Unset `SCRIPT_AES256_ENCRYPTION_KEY` in your environment "
|
||||
"or make sure that it contains exactly 64 hexadecimal characters."
|
||||
)
|
||||
raise
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(
|
||||
f"""\
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
uint8_t script_encryption_key[32] = {{
|
||||
{methods.format_buffer(buffer, 1)}
|
||||
}};"""
|
||||
)
|
||||
|
||||
|
||||
def make_certs_header(target, source, env):
|
||||
buffer = methods.get_buffer(str(source[0]))
|
||||
decomp_size = len(buffer)
|
||||
buffer = methods.compress_buffer(buffer)
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
# System certs path. Editor will use them if defined. (for package maintainers)
|
||||
file.write(f'#define _SYSTEM_CERTS_PATH "{source[2]}"\n')
|
||||
if source[1].read():
|
||||
# Defined here and not in env so changing it does not trigger a full rebuild.
|
||||
file.write(f"""\
|
||||
#define BUILTIN_CERTS_ENABLED
|
||||
|
||||
inline constexpr int _certs_compressed_size = {len(buffer)};
|
||||
inline constexpr int _certs_uncompressed_size = {decomp_size};
|
||||
inline constexpr unsigned char _certs_compressed[] = {{
|
||||
{methods.format_buffer(buffer, 1)}
|
||||
}};
|
||||
""")
|
||||
|
||||
|
||||
def make_authors_header(target, source, env):
|
||||
SECTIONS = {
|
||||
"Project Founders": "AUTHORS_FOUNDERS",
|
||||
"Lead Developer": "AUTHORS_LEAD_DEVELOPERS",
|
||||
"Project Manager": "AUTHORS_PROJECT_MANAGERS",
|
||||
"Developers": "AUTHORS_DEVELOPERS",
|
||||
}
|
||||
buffer = methods.get_buffer(str(source[0]))
|
||||
reading = False
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
|
||||
def close_section():
|
||||
file.write("\tnullptr,\n};\n\n")
|
||||
|
||||
for line in buffer.decode().splitlines():
|
||||
if line.startswith(" ") and reading:
|
||||
file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
|
||||
elif line.startswith("## "):
|
||||
if reading:
|
||||
close_section()
|
||||
reading = False
|
||||
section = SECTIONS[line[3:].strip()]
|
||||
if section:
|
||||
file.write(f"inline constexpr const char *{section}[] = {{\n")
|
||||
reading = True
|
||||
|
||||
if reading:
|
||||
close_section()
|
||||
|
||||
|
||||
def make_donors_header(target, source, env):
|
||||
SECTIONS = {
|
||||
"Patrons": "DONORS_PATRONS",
|
||||
"Platinum sponsors": "DONORS_SPONSORS_PLATINUM",
|
||||
"Gold sponsors": "DONORS_SPONSORS_GOLD",
|
||||
"Silver sponsors": "DONORS_SPONSORS_SILVER",
|
||||
"Diamond members": "DONORS_MEMBERS_DIAMOND",
|
||||
"Titanium members": "DONORS_MEMBERS_TITANIUM",
|
||||
"Platinum members": "DONORS_MEMBERS_PLATINUM",
|
||||
"Gold members": "DONORS_MEMBERS_GOLD",
|
||||
}
|
||||
buffer = methods.get_buffer(str(source[0]))
|
||||
reading = False
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
|
||||
def close_section():
|
||||
file.write("\tnullptr,\n};\n\n")
|
||||
|
||||
for line in buffer.decode().splitlines():
|
||||
if line.startswith(" ") and reading:
|
||||
file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
|
||||
elif line.startswith("## "):
|
||||
if reading:
|
||||
close_section()
|
||||
reading = False
|
||||
section = SECTIONS.get(line[3:].strip())
|
||||
if section:
|
||||
file.write(f"inline constexpr const char *{section}[] = {{\n")
|
||||
reading = True
|
||||
|
||||
if reading:
|
||||
close_section()
|
||||
|
||||
|
||||
def make_license_header(target, source, env):
|
||||
src_copyright = str(source[0])
|
||||
src_license = str(source[1])
|
||||
|
||||
class LicenseReader:
|
||||
def __init__(self, license_file: TextIOWrapper):
|
||||
self._license_file = license_file
|
||||
self.line_num = 0
|
||||
self.current = self.next_line()
|
||||
|
||||
def next_line(self):
|
||||
line = self._license_file.readline()
|
||||
self.line_num += 1
|
||||
while line.startswith("#"):
|
||||
line = self._license_file.readline()
|
||||
self.line_num += 1
|
||||
self.current = line
|
||||
return line
|
||||
|
||||
def next_tag(self):
|
||||
if ":" not in self.current:
|
||||
return ("", [])
|
||||
tag, line = self.current.split(":", 1)
|
||||
lines = [line.strip()]
|
||||
while self.next_line() and self.current.startswith(" "):
|
||||
lines.append(self.current.strip())
|
||||
return (tag, lines)
|
||||
|
||||
projects = OrderedDict()
|
||||
license_list = []
|
||||
|
||||
with open(src_copyright, "r", encoding="utf-8") as copyright_file:
|
||||
reader = LicenseReader(copyright_file)
|
||||
part = {}
|
||||
while reader.current:
|
||||
tag, content = reader.next_tag()
|
||||
if tag in ("Files", "Copyright", "License"):
|
||||
part[tag] = content[:]
|
||||
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:
|
||||
# end of a paragraph start a new part
|
||||
if "License" in part and "Files" not in part:
|
||||
# no Files tag in this one, so assume standalone license
|
||||
license_list.append(part["License"])
|
||||
part = {}
|
||||
reader.next_line()
|
||||
|
||||
data_list = []
|
||||
for project in iter(projects.values()):
|
||||
for part in project:
|
||||
part["file_index"] = len(data_list)
|
||||
data_list += part["Files"]
|
||||
part["copyright_index"] = len(data_list)
|
||||
data_list += part["Copyright"]
|
||||
|
||||
with open(src_license, "r", encoding="utf-8") as file:
|
||||
license_text = file.read()
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(f"""\
|
||||
inline constexpr const char *GODOT_LICENSE_TEXT = {{
|
||||
{methods.to_raw_cstring(license_text)}
|
||||
}};
|
||||
|
||||
struct ComponentCopyrightPart {{
|
||||
const char *license;
|
||||
const char *const *files;
|
||||
const char *const *copyright_statements;
|
||||
int file_count;
|
||||
int copyright_count;
|
||||
}};
|
||||
|
||||
struct ComponentCopyright {{
|
||||
const char *name;
|
||||
const ComponentCopyrightPart *parts;
|
||||
int part_count;
|
||||
}};
|
||||
|
||||
""")
|
||||
|
||||
file.write("inline constexpr const char *COPYRIGHT_INFO_DATA[] = {\n")
|
||||
for line in data_list:
|
||||
file.write(f'\t"{methods.to_escaped_cstring(line)}",\n')
|
||||
file.write("};\n\n")
|
||||
|
||||
file.write("inline constexpr ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n")
|
||||
part_index = 0
|
||||
part_indexes = {}
|
||||
for project_name, project in iter(projects.items()):
|
||||
part_indexes[project_name] = part_index
|
||||
for part in project:
|
||||
file.write(
|
||||
f'\t{{ "{methods.to_escaped_cstring(part["License"][0])}", '
|
||||
+ f"©RIGHT_INFO_DATA[{part['file_index']}], "
|
||||
+ f"©RIGHT_INFO_DATA[{part['copyright_index']}], "
|
||||
+ f"{len(part['Files'])}, {len(part['Copyright'])} }},\n"
|
||||
)
|
||||
part_index += 1
|
||||
file.write("};\n\n")
|
||||
|
||||
file.write(f"inline constexpr int COPYRIGHT_INFO_COUNT = {len(projects)};\n")
|
||||
|
||||
file.write("inline constexpr ComponentCopyright COPYRIGHT_INFO[] = {\n")
|
||||
for project_name, project in iter(projects.items()):
|
||||
file.write(
|
||||
f'\t{{ "{methods.to_escaped_cstring(project_name)}", '
|
||||
+ f"©RIGHT_PROJECT_PARTS[{part_indexes[project_name]}], "
|
||||
+ f"{len(project)} }},\n"
|
||||
)
|
||||
file.write("};\n\n")
|
||||
|
||||
file.write(f"inline constexpr int LICENSE_COUNT = {len(license_list)};\n")
|
||||
|
||||
file.write("inline constexpr const char *LICENSE_NAMES[] = {\n")
|
||||
for license in license_list:
|
||||
file.write(f'\t"{methods.to_escaped_cstring(license[0])}",\n')
|
||||
file.write("};\n\n")
|
||||
|
||||
file.write("inline constexpr const char *LICENSE_BODIES[] = {\n\n")
|
||||
for license in license_list:
|
||||
to_raw = []
|
||||
for line in license[1:]:
|
||||
if line == ".":
|
||||
to_raw += [""]
|
||||
else:
|
||||
to_raw += [line]
|
||||
file.write(f"{methods.to_raw_cstring(to_raw)},\n\n")
|
||||
file.write("};\n\n")
|
||||
875
core/core_constants.cpp
Normal file
875
core/core_constants.cpp
Normal file
@@ -0,0 +1,875 @@
|
||||
/**************************************************************************/
|
||||
/* core_constants.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 "core_constants.h"
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
struct _CoreConstant {
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool ignore_value_in_docs = false;
|
||||
bool is_bitfield = false;
|
||||
#endif // DEBUG_ENABLED
|
||||
StringName enum_name;
|
||||
const char *name = nullptr;
|
||||
int64_t value = 0;
|
||||
|
||||
_CoreConstant() {}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
_CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value, bool p_ignore_value_in_docs = false, bool p_is_bitfield = false) :
|
||||
ignore_value_in_docs(p_ignore_value_in_docs),
|
||||
is_bitfield(p_is_bitfield),
|
||||
enum_name(p_enum_name),
|
||||
name(p_name),
|
||||
value(p_value) {
|
||||
}
|
||||
#else
|
||||
_CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value) :
|
||||
enum_name(p_enum_name),
|
||||
name(p_name),
|
||||
value(p_value) {
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
};
|
||||
|
||||
static Vector<_CoreConstant> _global_constants;
|
||||
static HashMap<StringName, int> _global_constants_map;
|
||||
static HashMap<StringName, Vector<_CoreConstant>> _global_enums;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
#define BIND_CORE_CONSTANT(m_constant) \
|
||||
_global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1;
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_FLAG(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, false, true)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
// This just binds enum classes as if they were regular enum constants.
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, false, true)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member, false, true)); \
|
||||
_global_constants_map[#m_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, true)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
|
||||
_global_constants_map[m_custom_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
|
||||
_global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant, true)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1;
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, true)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant, true)); \
|
||||
_global_constants_map[m_custom_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define BIND_CORE_CONSTANT(m_constant) \
|
||||
_global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1;
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_FLAG(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
// This just binds enum classes as if they were regular enum constants.
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \
|
||||
_global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
|
||||
_global_constants_map[m_custom_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \
|
||||
_global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1;
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \
|
||||
_global_constants_map[#m_constant] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
|
||||
{ \
|
||||
StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \
|
||||
_global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \
|
||||
_global_constants_map[m_custom_name] = _global_constants.size() - 1; \
|
||||
_global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void register_global_constants() {
|
||||
BIND_CORE_ENUM_CONSTANT(SIDE_LEFT);
|
||||
BIND_CORE_ENUM_CONSTANT(SIDE_TOP);
|
||||
BIND_CORE_ENUM_CONSTANT(SIDE_RIGHT);
|
||||
BIND_CORE_ENUM_CONSTANT(SIDE_BOTTOM);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(CORNER_TOP_LEFT);
|
||||
BIND_CORE_ENUM_CONSTANT(CORNER_TOP_RIGHT);
|
||||
BIND_CORE_ENUM_CONSTANT(CORNER_BOTTOM_RIGHT);
|
||||
BIND_CORE_ENUM_CONSTANT(CORNER_BOTTOM_LEFT);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(VERTICAL);
|
||||
BIND_CORE_ENUM_CONSTANT(HORIZONTAL);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(CLOCKWISE);
|
||||
BIND_CORE_ENUM_CONSTANT(COUNTERCLOCKWISE);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(HORIZONTAL_ALIGNMENT_LEFT);
|
||||
BIND_CORE_ENUM_CONSTANT(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
BIND_CORE_ENUM_CONSTANT(HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
BIND_CORE_ENUM_CONSTANT(HORIZONTAL_ALIGNMENT_FILL);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(VERTICAL_ALIGNMENT_TOP);
|
||||
BIND_CORE_ENUM_CONSTANT(VERTICAL_ALIGNMENT_CENTER);
|
||||
BIND_CORE_ENUM_CONSTANT(VERTICAL_ALIGNMENT_BOTTOM);
|
||||
BIND_CORE_ENUM_CONSTANT(VERTICAL_ALIGNMENT_FILL);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TOP_TO);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_CENTER_TO);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_BASELINE_TO);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_BOTTOM_TO);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TO_TOP);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TO_CENTER);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TO_BASELINE);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TO_BOTTOM);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TOP);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_CENTER);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_BOTTOM);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_IMAGE_MASK);
|
||||
BIND_CORE_ENUM_CONSTANT(INLINE_ALIGNMENT_TEXT_MASK);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, XYZ);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, XZY);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, YXZ);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, YZX);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, ZXY);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(EulerOrder, EULER_ORDER, ZYX);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, NONE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SPECIAL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ESCAPE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, TAB);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BACKTAB);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BACKSPACE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ENTER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_ENTER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, INSERT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_DELETE, KEY_DELETE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PAUSE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PRINT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SYSREQ);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, CLEAR);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, HOME);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, END);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, UP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, RIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, DOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PAGEUP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PAGEDOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SHIFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, CTRL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, META);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ALT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, CAPSLOCK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, NUMLOCK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SCROLLLOCK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F5);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F6);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F7);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F8);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F9);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F10);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F11);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F12);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F13);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F14);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F15);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F16);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F17);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F18);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F19);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F20);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F21);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F22);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F23);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F24);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F25);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F26);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F27);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F28);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F29);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F30);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F31);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F32);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F33);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F34);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F35);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_MULTIPLY);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_DIVIDE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_SUBTRACT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_PERIOD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_ADD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_0);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_5);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_6);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_7);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_8);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KP_9);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MENU);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, HYPER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, HELP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BACK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, FORWARD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, STOP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, REFRESH);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, VOLUMEDOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, VOLUMEMUTE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, VOLUMEUP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MEDIAPLAY);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MEDIASTOP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MEDIAPREVIOUS);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MEDIANEXT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MEDIARECORD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, HOMEPAGE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, FAVORITES);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SEARCH);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, STANDBY);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, OPENURL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHMAIL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHMEDIA);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH0);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH5);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH6);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH7);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH8);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCH9);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHA);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHB);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHC);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LAUNCHF);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, GLOBE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, KEYBOARD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, JIS_EISU);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, JIS_KANA);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, UNKNOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SPACE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, EXCLAM);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, QUOTEDBL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, NUMBERSIGN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, DOLLAR);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PERCENT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, AMPERSAND);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, APOSTROPHE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PARENLEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PARENRIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ASTERISK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PLUS);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, COMMA);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, MINUS);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, PERIOD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SLASH);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_0, KEY_0);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_1, KEY_1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_2, KEY_2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_3, KEY_3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_4, KEY_4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_5, KEY_5);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_6, KEY_6);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_7, KEY_7);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_8, KEY_8);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(Key, KEY_9, KEY_9);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, COLON);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SEMICOLON);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, LESS);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, EQUAL);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, GREATER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, QUESTION);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, AT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, A);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, B);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, C);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, D);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, E);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, F);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, G);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, H);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, I);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, J);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, K);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, L);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, M);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, N);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, O);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, P);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, Q);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, R);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, S);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, T);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, U);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, V);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, W);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, X);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, Y);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, Z);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BRACKETLEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BACKSLASH);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BRACKETRIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ASCIICIRCUM);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, UNDERSCORE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, QUOTELEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BRACELEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BAR);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, BRACERIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, ASCIITILDE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, YEN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(Key, KEY, SECTION);
|
||||
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(KeyModifierMask, KEY_CODE_MASK, CODE_MASK);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(KeyModifierMask, KEY_MODIFIER_MASK, MODIFIER_MASK);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, CMD_OR_CTRL);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, SHIFT);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, ALT);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, META);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, CTRL);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, KPAD);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, GROUP_SWITCH);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, UNSPECIFIED);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, RIGHT);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, NONE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, RIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, MIDDLE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, WHEEL_UP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, WHEEL_DOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, WHEEL_LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, WHEEL_RIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(MouseButton, MOUSE_BUTTON_XBUTTON1, MB_XBUTTON1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(MouseButton, MOUSE_BUTTON_XBUTTON2, MB_XBUTTON2);
|
||||
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, LEFT);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, RIGHT);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MIDDLE);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON1);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON2);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, INVALID);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, A);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, B);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, X);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, Y);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, BACK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, GUIDE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, START);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, LEFT_STICK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, RIGHT_STICK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, LEFT_SHOULDER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, RIGHT_SHOULDER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, DPAD_UP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, DPAD_DOWN);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, DPAD_LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, DPAD_RIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, MISC1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, PADDLE1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, PADDLE2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, PADDLE3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, PADDLE4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, TOUCHPAD);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, SDL_MAX);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, MAX);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, INVALID);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_X);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, LEFT_Y);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_X);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, RIGHT_Y);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_LEFT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, TRIGGER_RIGHT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, SDL_MAX);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyAxis, JOY_AXIS, MAX);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NONE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NOTE_OFF);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, NOTE_ON);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, AFTERTOUCH);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, CONTROL_CHANGE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, PROGRAM_CHANGE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, CHANNEL_PRESSURE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, PITCH_BEND);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, SYSTEM_EXCLUSIVE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, QUARTER_FRAME);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, SONG_POSITION_POINTER);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, SONG_SELECT);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, TUNE_REQUEST);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, TIMING_CLOCK);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, START);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, CONTINUE);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, STOP);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, ACTIVE_SENSING);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(MIDIMessage, MIDI_MESSAGE, SYSTEM_RESET);
|
||||
|
||||
// error list
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(OK); // (0)
|
||||
BIND_CORE_ENUM_CONSTANT(FAILED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_UNAVAILABLE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_UNCONFIGURED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_UNAUTHORIZED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_PARAMETER_RANGE_ERROR); // (5)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_OUT_OF_MEMORY);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_NOT_FOUND);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_BAD_DRIVE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_BAD_PATH);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_NO_PERMISSION); // (10)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_ALREADY_IN_USE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_CANT_OPEN);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_CANT_WRITE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_CANT_READ);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_UNRECOGNIZED); // (15)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_CORRUPT);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_MISSING_DEPENDENCIES);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_FILE_EOF);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_OPEN);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_CREATE); // (20)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_QUERY_FAILED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_ALREADY_IN_USE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_LOCKED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_TIMEOUT);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_CONNECT); // (25)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_RESOLVE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CONNECTION_ERROR);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_ACQUIRE_RESOURCE);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CANT_FORK);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_INVALID_DATA); // (30)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_INVALID_PARAMETER);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_ALREADY_EXISTS);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_DOES_NOT_EXIST);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_DATABASE_CANT_READ);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_DATABASE_CANT_WRITE); // (35)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_COMPILATION_FAILED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_METHOD_NOT_FOUND);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_LINK_FAILED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_SCRIPT_FAILED);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_CYCLIC_LINK); // (40)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_INVALID_DECLARATION);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_DUPLICATE_SYMBOL);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_PARSE_ERROR);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_BUSY);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_SKIP); // (45)
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_HELP);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_BUG);
|
||||
BIND_CORE_ENUM_CONSTANT(ERR_PRINTER_ON_FIRE);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NONE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_RANGE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ENUM);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ENUM_SUGGESTION);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_EXP_EASING);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LINK);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FLAGS);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_RENDER);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_PHYSICS);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_2D_NAVIGATION);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_RENDER);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_GLOBAL_FILE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_GLOBAL_DIR);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_RESOURCE_TYPE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MULTILINE_TEXT);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_EXPRESSION);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PLACEHOLDER_TEXT);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_COLOR_NO_ALPHA);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_OBJECT_ID);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_TYPE_STRING);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_OBJECT_TOO_BIG);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_PATH_VALID_TYPES);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_SAVE_FILE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_GLOBAL_SAVE_FILE);
|
||||
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_GROUP_ENABLE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INPUT_NAME);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE_PATH);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
|
||||
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NONE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_STORAGE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_INTERNAL);
|
||||
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_CHECKABLE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_CHECKED);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_GROUP);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_CATEGORY);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SUBGROUP);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_CLASS_IS_BITFIELD);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_INSTANCE_STATE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_RESTART_IF_CHANGED);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SCRIPT_VARIABLE);
|
||||
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_STORE_IF_NULL);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_CLASS_IS_ENUM);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NIL_IS_VARIANT);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_ARRAY);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_ALWAYS_DUPLICATE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NEVER_DUPLICATE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_HIGH_END_GFX);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_KEYING_INCREMENTS);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFERRED_SET_RESOURCE);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_BASIC_SETTING);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_READ_ONLY);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SECRET);
|
||||
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFAULT);
|
||||
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_EDITOR);
|
||||
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_NORMAL);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_EDITOR);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_CONST);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_VIRTUAL);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_VARARG);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_STATIC);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_OBJECT_CORE);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAG_VIRTUAL_REQUIRED);
|
||||
BIND_CORE_BITFIELD_FLAG(METHOD_FLAGS_DEFAULT);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NIL", Variant::NIL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_BOOL", Variant::BOOL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_INT", Variant::INT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_FLOAT", Variant::FLOAT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_STRING", Variant::STRING);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR2", Variant::VECTOR2);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR2I", Variant::VECTOR2I);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_RECT2", Variant::RECT2);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_RECT2I", Variant::RECT2I);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR3", Variant::VECTOR3);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR3I", Variant::VECTOR3I);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_TRANSFORM2D", Variant::TRANSFORM2D);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR4", Variant::VECTOR4);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_VECTOR4I", Variant::VECTOR4I);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PLANE", Variant::PLANE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_QUATERNION", Variant::QUATERNION);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_AABB", Variant::AABB);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_BASIS", Variant::BASIS);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_TRANSFORM3D", Variant::TRANSFORM3D);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PROJECTION", Variant::PROJECTION);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_COLOR", Variant::COLOR);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_STRING_NAME", Variant::STRING_NAME);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_NODE_PATH", Variant::NODE_PATH);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_RID", Variant::RID);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_OBJECT", Variant::OBJECT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_CALLABLE", Variant::CALLABLE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_SIGNAL", Variant::SIGNAL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_DICTIONARY", Variant::DICTIONARY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_ARRAY", Variant::ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_BYTE_ARRAY", Variant::PACKED_BYTE_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_INT32_ARRAY", Variant::PACKED_INT32_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_INT64_ARRAY", Variant::PACKED_INT64_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_FLOAT32_ARRAY", Variant::PACKED_FLOAT32_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_FLOAT64_ARRAY", Variant::PACKED_FLOAT64_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_STRING_ARRAY", Variant::PACKED_STRING_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR2_ARRAY", Variant::PACKED_VECTOR2_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR3_ARRAY", Variant::PACKED_VECTOR3_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_COLOR_ARRAY", Variant::PACKED_COLOR_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_PACKED_VECTOR4_ARRAY", Variant::PACKED_VECTOR4_ARRAY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("TYPE_MAX", Variant::VARIANT_MAX);
|
||||
|
||||
//comparison
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_EQUAL", Variant::OP_EQUAL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_NOT_EQUAL", Variant::OP_NOT_EQUAL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_LESS", Variant::OP_LESS);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_LESS_EQUAL", Variant::OP_LESS_EQUAL);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_GREATER", Variant::OP_GREATER);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_GREATER_EQUAL", Variant::OP_GREATER_EQUAL);
|
||||
//mathematic
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_ADD", Variant::OP_ADD);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_SUBTRACT", Variant::OP_SUBTRACT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_MULTIPLY", Variant::OP_MULTIPLY);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_DIVIDE", Variant::OP_DIVIDE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_NEGATE", Variant::OP_NEGATE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_POSITIVE", Variant::OP_POSITIVE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_MODULE", Variant::OP_MODULE);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_POWER", Variant::OP_POWER);
|
||||
//bitwise
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_SHIFT_LEFT", Variant::OP_SHIFT_LEFT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_SHIFT_RIGHT", Variant::OP_SHIFT_RIGHT);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_BIT_AND", Variant::OP_BIT_AND);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_BIT_OR", Variant::OP_BIT_OR);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_BIT_XOR", Variant::OP_BIT_XOR);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_BIT_NEGATE", Variant::OP_BIT_NEGATE);
|
||||
//logic
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_AND", Variant::OP_AND);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_OR", Variant::OP_OR);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_XOR", Variant::OP_XOR);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_NOT", Variant::OP_NOT);
|
||||
//containment
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_IN", Variant::OP_IN);
|
||||
BIND_CORE_ENUM_CONSTANT_CUSTOM("OP_MAX", Variant::OP_MAX);
|
||||
}
|
||||
|
||||
void unregister_global_constants() {
|
||||
_global_constants.clear();
|
||||
_global_constants_map.clear();
|
||||
_global_enums.clear();
|
||||
}
|
||||
|
||||
int CoreConstants::get_global_constant_count() {
|
||||
return _global_constants.size();
|
||||
}
|
||||
|
||||
StringName CoreConstants::get_global_constant_enum(int p_idx) {
|
||||
return _global_constants[p_idx].enum_name;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool CoreConstants::is_global_constant_bitfield(int p_idx) {
|
||||
return _global_constants[p_idx].is_bitfield;
|
||||
}
|
||||
|
||||
bool CoreConstants::get_ignore_value_in_docs(int p_idx) {
|
||||
return _global_constants[p_idx].ignore_value_in_docs;
|
||||
}
|
||||
#else
|
||||
bool CoreConstants::is_global_constant_bitfield(int p_idx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CoreConstants::get_ignore_value_in_docs(int p_idx) {
|
||||
return false;
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
const char *CoreConstants::get_global_constant_name(int p_idx) {
|
||||
return _global_constants[p_idx].name;
|
||||
}
|
||||
|
||||
int64_t CoreConstants::get_global_constant_value(int p_idx) {
|
||||
return _global_constants[p_idx].value;
|
||||
}
|
||||
|
||||
bool CoreConstants::is_global_constant(const StringName &p_name) {
|
||||
return _global_constants_map.has(p_name);
|
||||
}
|
||||
|
||||
int CoreConstants::get_global_constant_index(const StringName &p_name) {
|
||||
ERR_FAIL_COND_V_MSG(!_global_constants_map.has(p_name), -1, "Trying to get index of non-existing constant.");
|
||||
return _global_constants_map[p_name];
|
||||
}
|
||||
|
||||
bool CoreConstants::is_global_enum(const StringName &p_enum) {
|
||||
return _global_enums.has(p_enum);
|
||||
}
|
||||
|
||||
void CoreConstants::get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *p_values) {
|
||||
ERR_FAIL_NULL_MSG(p_values, "Trying to get enum values with null map.");
|
||||
ERR_FAIL_COND_MSG(!_global_enums.has(p_enum), "Trying to get values of non-existing enum.");
|
||||
for (const _CoreConstant &constant : _global_enums[p_enum]) {
|
||||
(*p_values)[constant.name] = constant.value;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
void CoreConstants::get_global_enums(List<StringName> *r_values) {
|
||||
for (const KeyValue<StringName, Vector<_CoreConstant>> &global_enum : _global_enums) {
|
||||
r_values->push_back(global_enum.key);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
52
core/core_constants.h
Normal file
52
core/core_constants.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/**************************************************************************/
|
||||
/* core_constants.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
class CoreConstants {
|
||||
public:
|
||||
static int get_global_constant_count();
|
||||
static StringName get_global_constant_enum(int p_idx);
|
||||
static bool is_global_constant_bitfield(int p_idx);
|
||||
static bool get_ignore_value_in_docs(int p_idx);
|
||||
static const char *get_global_constant_name(int p_idx);
|
||||
static int64_t get_global_constant_value(int p_idx);
|
||||
static bool is_global_constant(const StringName &p_name);
|
||||
static int get_global_constant_index(const StringName &p_name);
|
||||
static bool is_global_enum(const StringName &p_enum);
|
||||
static void get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *r_values);
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void get_global_enums(List<StringName> *r_values);
|
||||
#endif
|
||||
};
|
||||
41
core/core_globals.h
Normal file
41
core/core_globals.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* core_globals.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Home for state needed from global functions
|
||||
// that cannot be stored in Engine or OS due to e.g. circular includes
|
||||
|
||||
class CoreGlobals {
|
||||
public:
|
||||
static inline bool leak_reporting_enabled = true;
|
||||
static inline bool print_line_enabled = true;
|
||||
static inline bool print_error_enabled = true;
|
||||
};
|
||||
88
core/core_string_names.h
Normal file
88
core/core_string_names.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/**************************************************************************/
|
||||
/* core_string_names.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
|
||||
class CoreStringNames {
|
||||
inline static CoreStringNames *singleton = nullptr;
|
||||
|
||||
public:
|
||||
static void create() { singleton = memnew(CoreStringNames); }
|
||||
static void free() {
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ static CoreStringNames *get_singleton() { return singleton; }
|
||||
|
||||
const StringName free_ = "free"; // free would conflict with C++ keyword.
|
||||
const StringName changed = "changed";
|
||||
const StringName script = "script";
|
||||
const StringName script_changed = "script_changed";
|
||||
const StringName _iter_init = "_iter_init";
|
||||
const StringName _iter_next = "_iter_next";
|
||||
const StringName _iter_get = "_iter_get";
|
||||
const StringName get_rid = "get_rid";
|
||||
const StringName _to_string = "_to_string";
|
||||
const StringName _custom_features = "_custom_features";
|
||||
|
||||
const StringName x = "x";
|
||||
const StringName y = "y";
|
||||
const StringName z = "z";
|
||||
const StringName w = "w";
|
||||
const StringName r = "r";
|
||||
const StringName g = "g";
|
||||
const StringName b = "b";
|
||||
const StringName a = "a";
|
||||
const StringName position = "position";
|
||||
const StringName size = "size";
|
||||
const StringName end = "end";
|
||||
const StringName basis = "basis";
|
||||
const StringName origin = "origin";
|
||||
const StringName normal = "normal";
|
||||
const StringName d = "d";
|
||||
const StringName h = "h";
|
||||
const StringName s = "s";
|
||||
const StringName v = "v";
|
||||
const StringName r8 = "r8";
|
||||
const StringName g8 = "g8";
|
||||
const StringName b8 = "b8";
|
||||
const StringName a8 = "a8";
|
||||
|
||||
const StringName call = "call";
|
||||
const StringName call_deferred = "call_deferred";
|
||||
const StringName bind = "bind";
|
||||
const StringName notification = "notification";
|
||||
const StringName property_list_changed = "property_list_changed";
|
||||
};
|
||||
|
||||
#define CoreStringName(m_name) CoreStringNames::get_singleton()->m_name
|
||||
65
core/crypto/SCsub
Normal file
65
core/crypto/SCsub
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env_crypto = env.Clone()
|
||||
|
||||
is_builtin = env["builtin_mbedtls"]
|
||||
has_module = env["module_mbedtls_enabled"]
|
||||
thirdparty_obj = []
|
||||
|
||||
if is_builtin or not has_module:
|
||||
# Use our headers for builtin or if the module is not going to be compiled.
|
||||
# We decided not to depend on system mbedtls just for these few files that can
|
||||
# be easily extracted.
|
||||
env_crypto.Prepend(CPPEXTPATH=["#thirdparty/mbedtls/include"])
|
||||
|
||||
# MbedTLS core functions (for CryptoCore).
|
||||
# If the mbedtls module is compiled we don't need to add the .c files with our
|
||||
# custom config since they will be built by the module itself.
|
||||
# Only if the module is not enabled, we must compile here the required sources
|
||||
# to make a "light" build with only the necessary mbedtls files.
|
||||
if not has_module:
|
||||
# Minimal mbedTLS config file
|
||||
config_path = "thirdparty/mbedtls/include/godot_core_mbedtls_config.h"
|
||||
config_path = f"<{config_path}>" if env_crypto["ninja"] and env_crypto.msvc else f'\\"{config_path}\\"'
|
||||
env_crypto.Append(CPPDEFINES=[("MBEDTLS_CONFIG_FILE", config_path)])
|
||||
# Build minimal mbedTLS library (MD5/SHA/Base64/AES).
|
||||
env_thirdparty = env_crypto.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/"
|
||||
thirdparty_mbedtls_sources = [
|
||||
"aes.c",
|
||||
"base64.c",
|
||||
"constant_time.c",
|
||||
"ctr_drbg.c",
|
||||
"entropy.c",
|
||||
"md.c",
|
||||
"md5.c",
|
||||
"sha1.c",
|
||||
"sha256.c",
|
||||
"godot_core_mbedtls_platform.c",
|
||||
]
|
||||
thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources]
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_mbedtls_sources)
|
||||
# Needed to force rebuilding the library when the configuration file is updated.
|
||||
env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h")
|
||||
env.core_sources += thirdparty_obj
|
||||
elif is_builtin:
|
||||
# Module mbedTLS config file
|
||||
config_path = "thirdparty/mbedtls/include/godot_module_mbedtls_config.h"
|
||||
config_path = f"<{config_path}>" if env_crypto["ninja"] and env_crypto.msvc else f'\\"{config_path}\\"'
|
||||
env_crypto.Append(CPPDEFINES=[("MBEDTLS_CONFIG_FILE", config_path)])
|
||||
# Needed to force rebuilding the core files when the configuration file is updated.
|
||||
thirdparty_obj = ["#thirdparty/mbedtls/include/godot_module_mbedtls_config.h"]
|
||||
|
||||
# Godot source files
|
||||
|
||||
core_obj = []
|
||||
|
||||
env_crypto.add_source_files(core_obj, "*.cpp")
|
||||
env.core_sources += core_obj
|
||||
|
||||
# Needed to force rebuilding the core files when the thirdparty library is updated.
|
||||
env.Depends(core_obj, thirdparty_obj)
|
||||
115
core/crypto/aes_context.cpp
Normal file
115
core/crypto/aes_context.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
/**************************************************************************/
|
||||
/* aes_context.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 "core/crypto/aes_context.h"
|
||||
|
||||
Error AESContext::start(Mode p_mode, const PackedByteArray &p_key, const PackedByteArray &p_iv) {
|
||||
ERR_FAIL_COND_V_MSG(mode != MODE_MAX, ERR_ALREADY_IN_USE, "AESContext already started. Call 'finish' before starting a new one.");
|
||||
ERR_FAIL_COND_V_MSG(p_mode < 0 || p_mode >= MODE_MAX, ERR_INVALID_PARAMETER, "Invalid mode requested.");
|
||||
// Key check.
|
||||
int key_bits = p_key.size() << 3;
|
||||
ERR_FAIL_COND_V_MSG(key_bits != 128 && key_bits != 256, ERR_INVALID_PARAMETER, "AES key must be either 16 or 32 bytes");
|
||||
// Initialization vector.
|
||||
if (p_mode == MODE_CBC_ENCRYPT || p_mode == MODE_CBC_DECRYPT) {
|
||||
ERR_FAIL_COND_V_MSG(p_iv.size() != 16, ERR_INVALID_PARAMETER, "The initialization vector (IV) must be exactly 16 bytes.");
|
||||
iv.resize(0);
|
||||
iv.append_array(p_iv);
|
||||
}
|
||||
// Encryption/decryption key.
|
||||
if (p_mode == MODE_CBC_ENCRYPT || p_mode == MODE_ECB_ENCRYPT) {
|
||||
ctx.set_encode_key(p_key.ptr(), key_bits);
|
||||
} else {
|
||||
ctx.set_decode_key(p_key.ptr(), key_bits);
|
||||
}
|
||||
mode = p_mode;
|
||||
return OK;
|
||||
}
|
||||
|
||||
PackedByteArray AESContext::update(const PackedByteArray &p_src) {
|
||||
ERR_FAIL_COND_V_MSG(mode < 0 || mode >= MODE_MAX, PackedByteArray(), "AESContext not started. Call 'start' before calling 'update'.");
|
||||
int len = p_src.size();
|
||||
ERR_FAIL_COND_V_MSG(len % 16, PackedByteArray(), "The number of bytes to be encrypted must be multiple of 16. Add padding if needed");
|
||||
PackedByteArray out;
|
||||
out.resize(len);
|
||||
const uint8_t *src_ptr = p_src.ptr();
|
||||
uint8_t *out_ptr = out.ptrw();
|
||||
switch (mode) {
|
||||
case MODE_ECB_ENCRYPT: {
|
||||
for (int i = 0; i < len; i += 16) {
|
||||
Error err = ctx.encrypt_ecb(src_ptr + i, out_ptr + i);
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
}
|
||||
} break;
|
||||
case MODE_ECB_DECRYPT: {
|
||||
for (int i = 0; i < len; i += 16) {
|
||||
Error err = ctx.decrypt_ecb(src_ptr + i, out_ptr + i);
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
}
|
||||
} break;
|
||||
case MODE_CBC_ENCRYPT: {
|
||||
Error err = ctx.encrypt_cbc(len, iv.ptrw(), p_src.ptr(), out.ptrw());
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
} break;
|
||||
case MODE_CBC_DECRYPT: {
|
||||
Error err = ctx.decrypt_cbc(len, iv.ptrw(), p_src.ptr(), out.ptrw());
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
} break;
|
||||
default:
|
||||
ERR_FAIL_V_MSG(PackedByteArray(), "Bug!");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
PackedByteArray AESContext::get_iv_state() {
|
||||
ERR_FAIL_COND_V_MSG(mode != MODE_CBC_ENCRYPT && mode != MODE_CBC_DECRYPT, PackedByteArray(), "Calling 'get_iv_state' only makes sense when the context is started in CBC mode.");
|
||||
PackedByteArray out;
|
||||
out.append_array(iv);
|
||||
return out;
|
||||
}
|
||||
|
||||
void AESContext::finish() {
|
||||
mode = MODE_MAX;
|
||||
iv.resize(0);
|
||||
}
|
||||
|
||||
void AESContext::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("start", "mode", "key", "iv"), &AESContext::start, DEFVAL(PackedByteArray()));
|
||||
ClassDB::bind_method(D_METHOD("update", "src"), &AESContext::update);
|
||||
ClassDB::bind_method(D_METHOD("get_iv_state"), &AESContext::get_iv_state);
|
||||
ClassDB::bind_method(D_METHOD("finish"), &AESContext::finish);
|
||||
BIND_ENUM_CONSTANT(MODE_ECB_ENCRYPT);
|
||||
BIND_ENUM_CONSTANT(MODE_ECB_DECRYPT);
|
||||
BIND_ENUM_CONSTANT(MODE_CBC_ENCRYPT);
|
||||
BIND_ENUM_CONSTANT(MODE_CBC_DECRYPT);
|
||||
BIND_ENUM_CONSTANT(MODE_MAX);
|
||||
}
|
||||
|
||||
AESContext::AESContext() {
|
||||
}
|
||||
65
core/crypto/aes_context.h
Normal file
65
core/crypto/aes_context.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**************************************************************************/
|
||||
/* aes_context.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class AESContext : public RefCounted {
|
||||
GDCLASS(AESContext, RefCounted);
|
||||
|
||||
public:
|
||||
enum Mode : int32_t {
|
||||
MODE_ECB_ENCRYPT,
|
||||
MODE_ECB_DECRYPT,
|
||||
MODE_CBC_ENCRYPT,
|
||||
MODE_CBC_DECRYPT,
|
||||
MODE_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
Mode mode = MODE_MAX;
|
||||
CryptoCore::AESContext ctx;
|
||||
PackedByteArray iv;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Error start(Mode p_mode, const PackedByteArray &p_key, const PackedByteArray &p_iv = PackedByteArray());
|
||||
PackedByteArray update(const PackedByteArray &p_src);
|
||||
PackedByteArray get_iv_state();
|
||||
void finish();
|
||||
|
||||
AESContext();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AESContext::Mode);
|
||||
259
core/crypto/crypto.cpp
Normal file
259
core/crypto/crypto.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
/**************************************************************************/
|
||||
/* crypto.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 "crypto.h"
|
||||
|
||||
/// Resources
|
||||
|
||||
CryptoKey *(*CryptoKey::_create)(bool p_notify_postinitialize) = nullptr;
|
||||
CryptoKey *CryptoKey::create(bool p_notify_postinitialize) {
|
||||
if (_create) {
|
||||
return _create(p_notify_postinitialize);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CryptoKey::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("save", "path", "public_only"), &CryptoKey::save, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("load", "path", "public_only"), &CryptoKey::load, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_public_only"), &CryptoKey::is_public_only);
|
||||
ClassDB::bind_method(D_METHOD("save_to_string", "public_only"), &CryptoKey::save_to_string, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("load_from_string", "string_key", "public_only"), &CryptoKey::load_from_string, DEFVAL(false));
|
||||
}
|
||||
|
||||
X509Certificate *(*X509Certificate::_create)(bool p_notify_postinitialize) = nullptr;
|
||||
X509Certificate *X509Certificate::create(bool p_notify_postinitialize) {
|
||||
if (_create) {
|
||||
return _create(p_notify_postinitialize);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void X509Certificate::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("save", "path"), &X509Certificate::save);
|
||||
ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);
|
||||
ClassDB::bind_method(D_METHOD("save_to_string"), &X509Certificate::save_to_string);
|
||||
ClassDB::bind_method(D_METHOD("load_from_string", "string"), &X509Certificate::load_from_string);
|
||||
}
|
||||
|
||||
/// TLSOptions
|
||||
|
||||
Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) {
|
||||
Ref<TLSOptions> opts;
|
||||
opts.instantiate();
|
||||
opts->mode = MODE_CLIENT;
|
||||
opts->trusted_ca_chain = p_trusted_chain;
|
||||
opts->common_name = p_common_name_override;
|
||||
return opts;
|
||||
}
|
||||
|
||||
Ref<TLSOptions> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) {
|
||||
Ref<TLSOptions> opts;
|
||||
opts.instantiate();
|
||||
opts->mode = MODE_CLIENT_UNSAFE;
|
||||
opts->trusted_ca_chain = p_trusted_chain;
|
||||
return opts;
|
||||
}
|
||||
|
||||
Ref<TLSOptions> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) {
|
||||
Ref<TLSOptions> opts;
|
||||
opts.instantiate();
|
||||
opts->mode = MODE_SERVER;
|
||||
opts->own_certificate = p_own_certificate;
|
||||
opts->private_key = p_own_key;
|
||||
return opts;
|
||||
}
|
||||
|
||||
void TLSOptions::_bind_methods() {
|
||||
ClassDB::bind_static_method("TLSOptions", D_METHOD("client", "trusted_chain", "common_name_override"), &TLSOptions::client, DEFVAL(Ref<X509Certificate>()), DEFVAL(String()));
|
||||
ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>()));
|
||||
ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_server"), &TLSOptions::is_server);
|
||||
ClassDB::bind_method(D_METHOD("is_unsafe_client"), &TLSOptions::is_unsafe_client);
|
||||
ClassDB::bind_method(D_METHOD("get_common_name_override"), &TLSOptions::get_common_name_override);
|
||||
ClassDB::bind_method(D_METHOD("get_trusted_ca_chain"), &TLSOptions::get_trusted_ca_chain);
|
||||
ClassDB::bind_method(D_METHOD("get_private_key"), &TLSOptions::get_private_key);
|
||||
ClassDB::bind_method(D_METHOD("get_own_certificate"), &TLSOptions::get_own_certificate);
|
||||
}
|
||||
|
||||
/// HMACContext
|
||||
|
||||
void HMACContext::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("start", "hash_type", "key"), &HMACContext::start);
|
||||
ClassDB::bind_method(D_METHOD("update", "data"), &HMACContext::update);
|
||||
ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish);
|
||||
}
|
||||
|
||||
HMACContext *(*HMACContext::_create)(bool p_notify_postinitialize) = nullptr;
|
||||
HMACContext *HMACContext::create(bool p_notify_postinitialize) {
|
||||
if (_create) {
|
||||
return _create(p_notify_postinitialize);
|
||||
}
|
||||
ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled.");
|
||||
}
|
||||
|
||||
/// Crypto
|
||||
|
||||
void (*Crypto::_load_default_certificates)(const String &p_path) = nullptr;
|
||||
Crypto *(*Crypto::_create)(bool p_notify_postinitialize) = nullptr;
|
||||
Crypto *Crypto::create(bool p_notify_postinitialize) {
|
||||
if (_create) {
|
||||
return _create(p_notify_postinitialize);
|
||||
}
|
||||
ERR_FAIL_V_MSG(nullptr, "Crypto is not available when the mbedtls module is disabled.");
|
||||
}
|
||||
|
||||
void Crypto::load_default_certificates(const String &p_path) {
|
||||
if (_load_default_certificates) {
|
||||
_load_default_certificates(p_path);
|
||||
}
|
||||
}
|
||||
|
||||
PackedByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, const PackedByteArray &p_key, const PackedByteArray &p_msg) {
|
||||
Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create());
|
||||
ERR_FAIL_COND_V_MSG(ctx.is_null(), PackedByteArray(), "HMAC is not available without mbedtls module.");
|
||||
Error err = ctx->start(p_hash_type, p_key);
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
err = ctx->update(p_msg);
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
return ctx->finish();
|
||||
}
|
||||
|
||||
// Compares two HMACS for equality without leaking timing information in order to prevent timing attacks.
|
||||
// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
bool Crypto::constant_time_compare(const PackedByteArray &p_trusted, const PackedByteArray &p_received) {
|
||||
const uint8_t *t = p_trusted.ptr();
|
||||
const uint8_t *r = p_received.ptr();
|
||||
int tlen = p_trusted.size();
|
||||
int rlen = p_received.size();
|
||||
// If the lengths are different then nothing else matters.
|
||||
if (tlen != rlen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t v = 0;
|
||||
for (int i = 0; i < tlen; i++) {
|
||||
v |= t[i] ^ r[i];
|
||||
}
|
||||
return v == 0;
|
||||
}
|
||||
|
||||
void Crypto::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes);
|
||||
ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa);
|
||||
ClassDB::bind_method(D_METHOD("generate_self_signed_certificate", "key", "issuer_name", "not_before", "not_after"), &Crypto::generate_self_signed_certificate, DEFVAL("CN=myserver,O=myorganisation,C=IT"), DEFVAL("20140101000000"), DEFVAL("20340101000000"));
|
||||
ClassDB::bind_method(D_METHOD("sign", "hash_type", "hash", "key"), &Crypto::sign);
|
||||
ClassDB::bind_method(D_METHOD("verify", "hash_type", "hash", "signature", "key"), &Crypto::verify);
|
||||
ClassDB::bind_method(D_METHOD("encrypt", "key", "plaintext"), &Crypto::encrypt);
|
||||
ClassDB::bind_method(D_METHOD("decrypt", "key", "ciphertext"), &Crypto::decrypt);
|
||||
ClassDB::bind_method(D_METHOD("hmac_digest", "hash_type", "key", "msg"), &Crypto::hmac_digest);
|
||||
ClassDB::bind_method(D_METHOD("constant_time_compare", "trusted", "received"), &Crypto::constant_time_compare);
|
||||
}
|
||||
|
||||
/// Resource loader/saver
|
||||
|
||||
Ref<Resource> ResourceFormatLoaderCrypto::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
String el = p_path.get_extension().to_lower();
|
||||
if (el == "crt") {
|
||||
X509Certificate *cert = X509Certificate::create();
|
||||
if (cert) {
|
||||
cert->load(p_path);
|
||||
}
|
||||
return cert;
|
||||
} else if (el == "key") {
|
||||
CryptoKey *key = CryptoKey::create();
|
||||
if (key) {
|
||||
key->load(p_path, false);
|
||||
}
|
||||
return key;
|
||||
} else if (el == "pub") {
|
||||
CryptoKey *key = CryptoKey::create();
|
||||
if (key) {
|
||||
key->load(p_path, true);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ResourceFormatLoaderCrypto::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("crt");
|
||||
p_extensions->push_back("key");
|
||||
p_extensions->push_back("pub");
|
||||
}
|
||||
|
||||
bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const {
|
||||
return p_type == "X509Certificate" || p_type == "CryptoKey";
|
||||
}
|
||||
|
||||
String ResourceFormatLoaderCrypto::get_resource_type(const String &p_path) const {
|
||||
String el = p_path.get_extension().to_lower();
|
||||
if (el == "crt") {
|
||||
return "X509Certificate";
|
||||
} else if (el == "key" || el == "pub") {
|
||||
return "CryptoKey";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
|
||||
Error err;
|
||||
Ref<X509Certificate> cert = p_resource;
|
||||
Ref<CryptoKey> key = p_resource;
|
||||
if (cert.is_valid()) {
|
||||
err = cert->save(p_path);
|
||||
} else if (key.is_valid()) {
|
||||
String el = p_path.get_extension().to_lower();
|
||||
err = key->save(p_path, el == "pub");
|
||||
} else {
|
||||
ERR_FAIL_V(ERR_INVALID_PARAMETER);
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save Crypto resource to file '%s'.", p_path));
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ResourceFormatSaverCrypto::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
|
||||
const X509Certificate *cert = Object::cast_to<X509Certificate>(*p_resource);
|
||||
const CryptoKey *key = Object::cast_to<CryptoKey>(*p_resource);
|
||||
if (cert) {
|
||||
p_extensions->push_back("crt");
|
||||
}
|
||||
if (key) {
|
||||
if (!key->is_public_only()) {
|
||||
p_extensions->push_back("key");
|
||||
}
|
||||
p_extensions->push_back("pub");
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceFormatSaverCrypto::recognize(const Ref<Resource> &p_resource) const {
|
||||
return Object::cast_to<X509Certificate>(*p_resource) || Object::cast_to<CryptoKey>(*p_resource);
|
||||
}
|
||||
168
core/crypto/crypto.h
Normal file
168
core/crypto/crypto.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/**************************************************************************/
|
||||
/* crypto.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/hashing_context.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class CryptoKey : public Resource {
|
||||
GDCLASS(CryptoKey, Resource);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static CryptoKey *(*_create)(bool p_notify_postinitialize);
|
||||
|
||||
public:
|
||||
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;
|
||||
virtual Error load_from_string(const String &p_string_key, bool p_public_only = false) = 0;
|
||||
virtual bool is_public_only() const = 0;
|
||||
};
|
||||
|
||||
class X509Certificate : public Resource {
|
||||
GDCLASS(X509Certificate, Resource);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static X509Certificate *(*_create)(bool p_notify_postinitialize);
|
||||
|
||||
public:
|
||||
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;
|
||||
virtual String save_to_string() = 0;
|
||||
virtual Error load_from_string(const String &string) = 0;
|
||||
};
|
||||
|
||||
class TLSOptions : public RefCounted {
|
||||
GDCLASS(TLSOptions, RefCounted);
|
||||
|
||||
private:
|
||||
enum Mode {
|
||||
MODE_CLIENT = 0,
|
||||
MODE_CLIENT_UNSAFE = 1,
|
||||
MODE_SERVER = 2,
|
||||
};
|
||||
|
||||
Mode mode = MODE_CLIENT;
|
||||
String common_name;
|
||||
Ref<X509Certificate> trusted_ca_chain;
|
||||
Ref<X509Certificate> own_certificate;
|
||||
Ref<CryptoKey> private_key;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static Ref<TLSOptions> client(Ref<X509Certificate> p_trusted_chain = Ref<X509Certificate>(), const String &p_common_name_override = String());
|
||||
static Ref<TLSOptions> client_unsafe(Ref<X509Certificate> p_trusted_chain);
|
||||
static Ref<TLSOptions> server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate);
|
||||
|
||||
String get_common_name_override() const { return common_name; }
|
||||
Ref<X509Certificate> get_trusted_ca_chain() const { return trusted_ca_chain; }
|
||||
Ref<X509Certificate> get_own_certificate() const { return own_certificate; }
|
||||
Ref<CryptoKey> get_private_key() const { return private_key; }
|
||||
bool is_server() const { return mode == MODE_SERVER; }
|
||||
bool is_unsafe_client() const { return mode == MODE_CLIENT_UNSAFE; }
|
||||
};
|
||||
|
||||
class HMACContext : public RefCounted {
|
||||
GDCLASS(HMACContext, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static HMACContext *(*_create)(bool p_notify_postinitialize);
|
||||
|
||||
public:
|
||||
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;
|
||||
virtual PackedByteArray finish() = 0;
|
||||
|
||||
HMACContext() {}
|
||||
virtual ~HMACContext() {}
|
||||
};
|
||||
|
||||
class Crypto : public RefCounted {
|
||||
GDCLASS(Crypto, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static Crypto *(*_create)(bool p_notify_postinitialize);
|
||||
static void (*_load_default_certificates)(const String &p_path);
|
||||
|
||||
public:
|
||||
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;
|
||||
virtual Ref<CryptoKey> generate_rsa(int p_bytes) = 0;
|
||||
virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, const String &p_issuer_name, const String &p_not_before, const String &p_not_after) = 0;
|
||||
|
||||
virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, Ref<CryptoKey> p_key) = 0;
|
||||
virtual bool verify(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, const Vector<uint8_t> &p_signature, Ref<CryptoKey> p_key) = 0;
|
||||
virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_plaintext) = 0;
|
||||
virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_ciphertext) = 0;
|
||||
|
||||
PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, const PackedByteArray &p_key, const PackedByteArray &p_msg);
|
||||
|
||||
// Compares two PackedByteArrays for equality without leaking timing information in order to prevent timing attacks.
|
||||
// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
bool constant_time_compare(const PackedByteArray &p_trusted, const PackedByteArray &p_received);
|
||||
|
||||
Crypto() {}
|
||||
};
|
||||
|
||||
class ResourceFormatLoaderCrypto : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
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 {
|
||||
public:
|
||||
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
|
||||
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
|
||||
virtual bool recognize(const Ref<Resource> &p_resource) const override;
|
||||
};
|
||||
251
core/crypto/crypto_core.cpp
Normal file
251
core/crypto/crypto_core.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
/**************************************************************************/
|
||||
/* crypto_core.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 "crypto_core.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#if MBEDTLS_VERSION_MAJOR >= 3
|
||||
#include <mbedtls/compat-2.x.h>
|
||||
#endif
|
||||
|
||||
// RandomGenerator
|
||||
CryptoCore::RandomGenerator::RandomGenerator() {
|
||||
entropy = memalloc(sizeof(mbedtls_entropy_context));
|
||||
mbedtls_entropy_init((mbedtls_entropy_context *)entropy);
|
||||
mbedtls_entropy_add_source((mbedtls_entropy_context *)entropy, &CryptoCore::RandomGenerator::_entropy_poll, nullptr, 256, MBEDTLS_ENTROPY_SOURCE_STRONG);
|
||||
ctx = memalloc(sizeof(mbedtls_ctr_drbg_context));
|
||||
mbedtls_ctr_drbg_init((mbedtls_ctr_drbg_context *)ctx);
|
||||
}
|
||||
|
||||
CryptoCore::RandomGenerator::~RandomGenerator() {
|
||||
mbedtls_ctr_drbg_free((mbedtls_ctr_drbg_context *)ctx);
|
||||
memfree(ctx);
|
||||
mbedtls_entropy_free((mbedtls_entropy_context *)entropy);
|
||||
memfree(entropy);
|
||||
}
|
||||
|
||||
int CryptoCore::RandomGenerator::_entropy_poll(void *p_data, unsigned char *r_buffer, size_t p_len, size_t *r_len) {
|
||||
*r_len = 0;
|
||||
Error err = OS::get_singleton()->get_entropy(r_buffer, p_len);
|
||||
ERR_FAIL_COND_V(err, MBEDTLS_ERR_ENTROPY_SOURCE_FAILED);
|
||||
*r_len = p_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
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, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
|
||||
return OK;
|
||||
}
|
||||
|
||||
// MD5
|
||||
CryptoCore::MD5Context::MD5Context() {
|
||||
ctx = memalloc(sizeof(mbedtls_md5_context));
|
||||
mbedtls_md5_init((mbedtls_md5_context *)ctx);
|
||||
}
|
||||
|
||||
CryptoCore::MD5Context::~MD5Context() {
|
||||
mbedtls_md5_free((mbedtls_md5_context *)ctx);
|
||||
memfree((mbedtls_md5_context *)ctx);
|
||||
}
|
||||
|
||||
Error CryptoCore::MD5Context::start() {
|
||||
int ret = mbedtls_md5_starts_ret((mbedtls_md5_context *)ctx);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::MD5Context::update(const uint8_t *p_src, size_t p_len) {
|
||||
int ret = mbedtls_md5_update_ret((mbedtls_md5_context *)ctx, p_src, p_len);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::MD5Context::finish(unsigned char r_hash[16]) {
|
||||
int ret = mbedtls_md5_finish_ret((mbedtls_md5_context *)ctx, r_hash);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
// SHA1
|
||||
CryptoCore::SHA1Context::SHA1Context() {
|
||||
ctx = memalloc(sizeof(mbedtls_sha1_context));
|
||||
mbedtls_sha1_init((mbedtls_sha1_context *)ctx);
|
||||
}
|
||||
|
||||
CryptoCore::SHA1Context::~SHA1Context() {
|
||||
mbedtls_sha1_free((mbedtls_sha1_context *)ctx);
|
||||
memfree((mbedtls_sha1_context *)ctx);
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA1Context::start() {
|
||||
int ret = mbedtls_sha1_starts_ret((mbedtls_sha1_context *)ctx);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA1Context::update(const uint8_t *p_src, size_t p_len) {
|
||||
int ret = mbedtls_sha1_update_ret((mbedtls_sha1_context *)ctx, p_src, p_len);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA1Context::finish(unsigned char r_hash[20]) {
|
||||
int ret = mbedtls_sha1_finish_ret((mbedtls_sha1_context *)ctx, r_hash);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
// SHA256
|
||||
CryptoCore::SHA256Context::SHA256Context() {
|
||||
ctx = memalloc(sizeof(mbedtls_sha256_context));
|
||||
mbedtls_sha256_init((mbedtls_sha256_context *)ctx);
|
||||
}
|
||||
|
||||
CryptoCore::SHA256Context::~SHA256Context() {
|
||||
mbedtls_sha256_free((mbedtls_sha256_context *)ctx);
|
||||
memfree((mbedtls_sha256_context *)ctx);
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA256Context::start() {
|
||||
int ret = mbedtls_sha256_starts_ret((mbedtls_sha256_context *)ctx, 0);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA256Context::update(const uint8_t *p_src, size_t p_len) {
|
||||
int ret = mbedtls_sha256_update_ret((mbedtls_sha256_context *)ctx, p_src, p_len);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::SHA256Context::finish(unsigned char r_hash[32]) {
|
||||
int ret = mbedtls_sha256_finish_ret((mbedtls_sha256_context *)ctx, r_hash);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
// AES256
|
||||
CryptoCore::AESContext::AESContext() {
|
||||
ctx = memalloc(sizeof(mbedtls_aes_context));
|
||||
mbedtls_aes_init((mbedtls_aes_context *)ctx);
|
||||
}
|
||||
|
||||
CryptoCore::AESContext::~AESContext() {
|
||||
mbedtls_aes_free((mbedtls_aes_context *)ctx);
|
||||
memfree((mbedtls_aes_context *)ctx);
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::set_encode_key(const uint8_t *p_key, size_t p_bits) {
|
||||
int ret = mbedtls_aes_setkey_enc((mbedtls_aes_context *)ctx, p_key, p_bits);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::set_decode_key(const uint8_t *p_key, size_t p_bits) {
|
||||
int ret = mbedtls_aes_setkey_dec((mbedtls_aes_context *)ctx, p_key, p_bits);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::encrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]) {
|
||||
int ret = mbedtls_aes_crypt_ecb((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
|
||||
int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, r_iv, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::encrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
|
||||
size_t iv_off = 0; // Ignore and assume 16-byte alignment.
|
||||
int ret = mbedtls_aes_crypt_cfb128((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, &iv_off, p_iv, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]) {
|
||||
int ret = mbedtls_aes_crypt_ecb((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::decrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
|
||||
int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_length, r_iv, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::AESContext::decrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
|
||||
size_t iv_off = 0; // Ignore and assume 16-byte alignment.
|
||||
int ret = mbedtls_aes_crypt_cfb128((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_length, &iv_off, p_iv, p_src, r_dst);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
// CryptoCore
|
||||
String CryptoCore::b64_encode_str(const uint8_t *p_src, size_t p_src_len) {
|
||||
size_t b64len = p_src_len / 3 * 4 + 4 + 1;
|
||||
Vector<uint8_t> b64buff;
|
||||
b64buff.resize(b64len);
|
||||
uint8_t *w64 = b64buff.ptrw();
|
||||
size_t strlen = 0;
|
||||
int ret = b64_encode(&w64[0], b64len, &strlen, p_src, p_src_len);
|
||||
w64[strlen] = 0;
|
||||
return ret ? String() : (const char *)&w64[0];
|
||||
}
|
||||
|
||||
Error CryptoCore::b64_encode(uint8_t *r_dst, size_t p_dst_len, size_t *r_len, const uint8_t *p_src, size_t p_src_len) {
|
||||
int ret = mbedtls_base64_encode(r_dst, p_dst_len, r_len, p_src, p_src_len);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::b64_decode(uint8_t *r_dst, size_t p_dst_len, size_t *r_len, const uint8_t *p_src, size_t p_src_len) {
|
||||
int ret = mbedtls_base64_decode(r_dst, p_dst_len, r_len, p_src, p_src_len);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::md5(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[16]) {
|
||||
int ret = mbedtls_md5_ret(p_src, p_src_len, r_hash);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::sha1(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[20]) {
|
||||
int ret = mbedtls_sha1_ret(p_src, p_src_len, r_hash);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
|
||||
Error CryptoCore::sha256(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[32]) {
|
||||
int ret = mbedtls_sha256_ret(p_src, p_src_len, r_hash, 0);
|
||||
return ret ? FAILED : OK;
|
||||
}
|
||||
116
core/crypto/crypto_core.h
Normal file
116
core/crypto/crypto_core.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/**************************************************************************/
|
||||
/* crypto_core.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class CryptoCore {
|
||||
public:
|
||||
class RandomGenerator {
|
||||
private:
|
||||
void *entropy = nullptr;
|
||||
void *ctx = nullptr;
|
||||
|
||||
static int _entropy_poll(void *p_data, unsigned char *r_buffer, size_t p_len, size_t *r_len);
|
||||
|
||||
public:
|
||||
RandomGenerator();
|
||||
~RandomGenerator();
|
||||
|
||||
Error init();
|
||||
Error get_random_bytes(uint8_t *r_buffer, size_t p_bytes);
|
||||
};
|
||||
|
||||
class MD5Context {
|
||||
private:
|
||||
void *ctx = nullptr;
|
||||
|
||||
public:
|
||||
MD5Context();
|
||||
~MD5Context();
|
||||
|
||||
Error start();
|
||||
Error update(const uint8_t *p_src, size_t p_len);
|
||||
Error finish(unsigned char r_hash[16]);
|
||||
};
|
||||
|
||||
class SHA1Context {
|
||||
private:
|
||||
void *ctx = nullptr;
|
||||
|
||||
public:
|
||||
SHA1Context();
|
||||
~SHA1Context();
|
||||
|
||||
Error start();
|
||||
Error update(const uint8_t *p_src, size_t p_len);
|
||||
Error finish(unsigned char r_hash[20]);
|
||||
};
|
||||
|
||||
class SHA256Context {
|
||||
private:
|
||||
void *ctx = nullptr;
|
||||
|
||||
public:
|
||||
SHA256Context();
|
||||
~SHA256Context();
|
||||
|
||||
Error start();
|
||||
Error update(const uint8_t *p_src, size_t p_len);
|
||||
Error finish(unsigned char r_hash[32]);
|
||||
};
|
||||
|
||||
class AESContext {
|
||||
private:
|
||||
void *ctx = nullptr;
|
||||
|
||||
public:
|
||||
AESContext();
|
||||
~AESContext();
|
||||
|
||||
Error set_encode_key(const uint8_t *p_key, size_t p_bits);
|
||||
Error set_decode_key(const uint8_t *p_key, size_t p_bits);
|
||||
Error encrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]);
|
||||
Error decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]);
|
||||
Error encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
|
||||
Error decrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
|
||||
Error encrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst);
|
||||
Error decrypt_cfb(size_t p_length, uint8_t p_iv[16], const uint8_t *p_src, uint8_t *r_dst);
|
||||
};
|
||||
|
||||
static String b64_encode_str(const uint8_t *p_src, size_t p_src_len);
|
||||
static Error b64_encode(uint8_t *r_dst, size_t p_dst_len, size_t *r_len, const uint8_t *p_src, size_t p_src_len);
|
||||
static Error b64_decode(uint8_t *r_dst, size_t p_dst_len, size_t *r_len, const uint8_t *p_src, size_t p_src_len);
|
||||
|
||||
static Error md5(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[16]);
|
||||
static Error sha1(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[20]);
|
||||
static Error sha256(const uint8_t *p_src, size_t p_src_len, unsigned char r_hash[32]);
|
||||
};
|
||||
134
core/crypto/hashing_context.cpp
Normal file
134
core/crypto/hashing_context.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/**************************************************************************/
|
||||
/* hashing_context.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 "hashing_context.h"
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
|
||||
Error HashingContext::start(HashType p_type) {
|
||||
ERR_FAIL_COND_V(ctx != nullptr, ERR_ALREADY_IN_USE);
|
||||
_create_ctx(p_type);
|
||||
ERR_FAIL_NULL_V(ctx, ERR_UNAVAILABLE);
|
||||
switch (type) {
|
||||
case HASH_MD5:
|
||||
return ((CryptoCore::MD5Context *)ctx)->start();
|
||||
case HASH_SHA1:
|
||||
return ((CryptoCore::SHA1Context *)ctx)->start();
|
||||
case HASH_SHA256:
|
||||
return ((CryptoCore::SHA256Context *)ctx)->start();
|
||||
}
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error HashingContext::update(const PackedByteArray &p_chunk) {
|
||||
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
|
||||
size_t len = p_chunk.size();
|
||||
ERR_FAIL_COND_V(len == 0, FAILED);
|
||||
const uint8_t *r = p_chunk.ptr();
|
||||
switch (type) {
|
||||
case HASH_MD5:
|
||||
return ((CryptoCore::MD5Context *)ctx)->update(&r[0], len);
|
||||
case HASH_SHA1:
|
||||
return ((CryptoCore::SHA1Context *)ctx)->update(&r[0], len);
|
||||
case HASH_SHA256:
|
||||
return ((CryptoCore::SHA256Context *)ctx)->update(&r[0], len);
|
||||
}
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
PackedByteArray HashingContext::finish() {
|
||||
ERR_FAIL_NULL_V(ctx, PackedByteArray());
|
||||
PackedByteArray out;
|
||||
Error err = FAILED;
|
||||
switch (type) {
|
||||
case HASH_MD5:
|
||||
out.resize(16);
|
||||
err = ((CryptoCore::MD5Context *)ctx)->finish(out.ptrw());
|
||||
break;
|
||||
case HASH_SHA1:
|
||||
out.resize(20);
|
||||
err = ((CryptoCore::SHA1Context *)ctx)->finish(out.ptrw());
|
||||
break;
|
||||
case HASH_SHA256:
|
||||
out.resize(32);
|
||||
err = ((CryptoCore::SHA256Context *)ctx)->finish(out.ptrw());
|
||||
break;
|
||||
}
|
||||
_delete_ctx();
|
||||
ERR_FAIL_COND_V(err != OK, PackedByteArray());
|
||||
return out;
|
||||
}
|
||||
|
||||
void HashingContext::_create_ctx(HashType p_type) {
|
||||
type = p_type;
|
||||
switch (type) {
|
||||
case HASH_MD5:
|
||||
ctx = memnew(CryptoCore::MD5Context);
|
||||
break;
|
||||
case HASH_SHA1:
|
||||
ctx = memnew(CryptoCore::SHA1Context);
|
||||
break;
|
||||
case HASH_SHA256:
|
||||
ctx = memnew(CryptoCore::SHA256Context);
|
||||
break;
|
||||
default:
|
||||
ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HashingContext::_delete_ctx() {
|
||||
switch (type) {
|
||||
case HASH_MD5:
|
||||
memdelete((CryptoCore::MD5Context *)ctx);
|
||||
break;
|
||||
case HASH_SHA1:
|
||||
memdelete((CryptoCore::SHA1Context *)ctx);
|
||||
break;
|
||||
case HASH_SHA256:
|
||||
memdelete((CryptoCore::SHA256Context *)ctx);
|
||||
break;
|
||||
}
|
||||
ctx = nullptr;
|
||||
}
|
||||
|
||||
void HashingContext::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("start", "type"), &HashingContext::start);
|
||||
ClassDB::bind_method(D_METHOD("update", "chunk"), &HashingContext::update);
|
||||
ClassDB::bind_method(D_METHOD("finish"), &HashingContext::finish);
|
||||
BIND_ENUM_CONSTANT(HASH_MD5);
|
||||
BIND_ENUM_CONSTANT(HASH_SHA1);
|
||||
BIND_ENUM_CONSTANT(HASH_SHA256);
|
||||
}
|
||||
|
||||
HashingContext::~HashingContext() {
|
||||
if (ctx != nullptr) {
|
||||
_delete_ctx();
|
||||
}
|
||||
}
|
||||
63
core/crypto/hashing_context.h
Normal file
63
core/crypto/hashing_context.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/**************************************************************************/
|
||||
/* hashing_context.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class HashingContext : public RefCounted {
|
||||
GDCLASS(HashingContext, RefCounted);
|
||||
|
||||
public:
|
||||
enum HashType : int32_t {
|
||||
HASH_MD5,
|
||||
HASH_SHA1,
|
||||
HASH_SHA256
|
||||
};
|
||||
|
||||
private:
|
||||
void *ctx = nullptr;
|
||||
HashType type = HASH_MD5;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _create_ctx(HashType p_type);
|
||||
void _delete_ctx();
|
||||
|
||||
public:
|
||||
Error start(HashType p_type);
|
||||
Error update(const PackedByteArray &p_chunk);
|
||||
PackedByteArray finish();
|
||||
|
||||
HashingContext() {}
|
||||
~HashingContext();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(HashingContext::HashType);
|
||||
6
core/debugger/SCsub
Normal file
6
core/debugger/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.core_sources, "*.cpp")
|
||||
179
core/debugger/debugger_marshalls.cpp
Normal file
179
core/debugger/debugger_marshalls.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
/**************************************************************************/
|
||||
/* debugger_marshalls.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 "debugger_marshalls.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
|
||||
#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
|
||||
#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
|
||||
|
||||
Array DebuggerMarshalls::ScriptStackDump::serialize() {
|
||||
Array arr = { frames.size() * 3 };
|
||||
for (const ScriptLanguage::StackInfo &frame : frames) {
|
||||
arr.push_back(frame.file);
|
||||
arr.push_back(frame.line);
|
||||
arr.push_back(frame.func);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
bool DebuggerMarshalls::ScriptStackDump::deserialize(const Array &p_arr) {
|
||||
CHECK_SIZE(p_arr, 1, "ScriptStackDump");
|
||||
uint32_t size = p_arr[0];
|
||||
CHECK_SIZE(p_arr, size, "ScriptStackDump");
|
||||
int idx = 1;
|
||||
for (uint32_t i = 0; i < size / 3; i++) {
|
||||
ScriptLanguage::StackInfo sf;
|
||||
sf.file = p_arr[idx];
|
||||
sf.line = p_arr[idx + 1];
|
||||
sf.func = p_arr[idx + 2];
|
||||
frames.push_back(sf);
|
||||
idx += 3;
|
||||
}
|
||||
CHECK_END(p_arr, idx, "ScriptStackDump");
|
||||
return true;
|
||||
}
|
||||
|
||||
Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
|
||||
Array arr = { name, type, value.get_type() };
|
||||
|
||||
Variant var = value;
|
||||
if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
|
||||
var = Variant();
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
Error err = encode_variant(var, nullptr, len, false);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Failed to encode variant.");
|
||||
}
|
||||
|
||||
if (len > max_size) {
|
||||
arr.push_back(Variant());
|
||||
} else {
|
||||
arr.push_back(var);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
|
||||
CHECK_SIZE(p_arr, 4, "ScriptStackVariable");
|
||||
name = p_arr[0];
|
||||
type = p_arr[1];
|
||||
var_type = p_arr[2];
|
||||
value = p_arr[3];
|
||||
CHECK_END(p_arr, 4, "ScriptStackVariable");
|
||||
return true;
|
||||
}
|
||||
|
||||
Array DebuggerMarshalls::OutputError::serialize() {
|
||||
unsigned int size = callstack.size();
|
||||
Array arr = {
|
||||
hr,
|
||||
min,
|
||||
sec, msec,
|
||||
source_file,
|
||||
source_func,
|
||||
source_line,
|
||||
error,
|
||||
error_descr,
|
||||
warning,
|
||||
size * 3
|
||||
};
|
||||
const ScriptLanguage::StackInfo *r = callstack.ptr();
|
||||
for (int i = 0; i < callstack.size(); i++) {
|
||||
arr.push_back(r[i].file);
|
||||
arr.push_back(r[i].func);
|
||||
arr.push_back(r[i].line);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
|
||||
CHECK_SIZE(p_arr, 11, "OutputError");
|
||||
hr = p_arr[0];
|
||||
min = p_arr[1];
|
||||
sec = p_arr[2];
|
||||
msec = p_arr[3];
|
||||
source_file = p_arr[4];
|
||||
source_func = p_arr[5];
|
||||
source_line = p_arr[6];
|
||||
error = p_arr[7];
|
||||
error_descr = p_arr[8];
|
||||
warning = p_arr[9];
|
||||
unsigned int stack_size = p_arr[10];
|
||||
CHECK_SIZE(p_arr, stack_size, "OutputError");
|
||||
int idx = 11;
|
||||
callstack.resize(stack_size / 3);
|
||||
ScriptLanguage::StackInfo *w = callstack.ptrw();
|
||||
for (unsigned int i = 0; i < stack_size / 3; i++) {
|
||||
w[i].file = p_arr[idx];
|
||||
w[i].func = p_arr[idx + 1];
|
||||
w[i].line = p_arr[idx + 2];
|
||||
idx += 3;
|
||||
}
|
||||
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;
|
||||
}
|
||||
74
core/debugger/debugger_marshalls.h
Normal file
74
core/debugger/debugger_marshalls.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/**************************************************************************/
|
||||
/* debugger_marshalls.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/shortcut.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
struct DebuggerMarshalls {
|
||||
struct ScriptStackVariable {
|
||||
String name;
|
||||
Variant value;
|
||||
int type = -1;
|
||||
int var_type = -1;
|
||||
|
||||
Array serialize(int max_size = 1 << 20); // 1 MiB default.
|
||||
bool deserialize(const Array &p_arr);
|
||||
};
|
||||
|
||||
struct ScriptStackDump {
|
||||
List<ScriptLanguage::StackInfo> frames;
|
||||
ScriptStackDump() {}
|
||||
|
||||
Array serialize();
|
||||
bool deserialize(const Array &p_arr);
|
||||
};
|
||||
|
||||
struct OutputError {
|
||||
int hr = -1;
|
||||
int min = -1;
|
||||
int sec = -1;
|
||||
int msec = -1;
|
||||
String source_file;
|
||||
String source_func;
|
||||
int source_line = -1;
|
||||
String error;
|
||||
String error_descr;
|
||||
bool warning = false;
|
||||
Vector<ScriptLanguage::StackInfo> callstack;
|
||||
|
||||
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);
|
||||
};
|
||||
196
core/debugger/engine_debugger.cpp
Normal file
196
core/debugger/engine_debugger.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/**************************************************************************/
|
||||
/* engine_debugger.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 "engine_debugger.h"
|
||||
|
||||
#include "core/debugger/local_debugger.h"
|
||||
#include "core/debugger/remote_debugger.h"
|
||||
#include "core/debugger/remote_debugger_peer.h"
|
||||
#include "core/debugger/script_debugger.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
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), 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), vformat("Profiler not registered: '%s'.", p_name));
|
||||
Profiler &p = profilers[p_name];
|
||||
if (p.active && p.toggle) {
|
||||
p.toggle(p.data, false, Array());
|
||||
p.active = false;
|
||||
}
|
||||
profilers.erase(p_name);
|
||||
}
|
||||
|
||||
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
|
||||
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), 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), 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), 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);
|
||||
}
|
||||
p.active = p_enabled;
|
||||
}
|
||||
|
||||
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool EngineDebugger::is_profiling(const StringName &p_name) {
|
||||
return profilers.has(p_name) && profilers[p_name].active;
|
||||
}
|
||||
|
||||
bool EngineDebugger::has_profiler(const StringName &p_name) {
|
||||
return profilers.has(p_name);
|
||||
}
|
||||
|
||||
bool EngineDebugger::has_capture(const StringName &p_name) {
|
||||
return captures.has(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, vformat("Capture not registered: '%s'.", p_name));
|
||||
const Capture &cap = captures[p_name];
|
||||
return cap.capture(cap.data, p_msg, p_args, r_captured);
|
||||
}
|
||||
|
||||
void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) {
|
||||
frame_time = USEC_TO_SEC(p_frame_ticks);
|
||||
process_time = USEC_TO_SEC(p_process_ticks);
|
||||
physics_time = USEC_TO_SEC(p_physics_ticks);
|
||||
physics_frame_time = p_physics_frame_time;
|
||||
// Notify tick to running profilers
|
||||
for (KeyValue<StringName, Profiler> &E : profilers) {
|
||||
Profiler &p = E.value;
|
||||
if (!p.active || !p.tick) {
|
||||
continue;
|
||||
}
|
||||
p.tick(p.data, frame_time, process_time, physics_time, physics_frame_time);
|
||||
}
|
||||
singleton->poll_events(true);
|
||||
}
|
||||
|
||||
void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, bool p_ignore_error_breaks, const Vector<String> &p_breakpoints, void (*p_allow_focus_steal_fn)()) {
|
||||
register_uri_handler("tcp://", RemoteDebuggerPeerTCP::create); // TCP is the default protocol. Platforms/modules can add more.
|
||||
if (p_uri.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (p_uri == "local://") {
|
||||
singleton = memnew(LocalDebugger);
|
||||
script_debugger = memnew(ScriptDebugger);
|
||||
// Tell the OS that we want to handle termination signals.
|
||||
OS::get_singleton()->initialize_debugging();
|
||||
} else if (p_uri.contains("://")) {
|
||||
const String proto = p_uri.substr(0, p_uri.find("://") + 3);
|
||||
if (!protocols.has(proto)) {
|
||||
return;
|
||||
}
|
||||
RemoteDebuggerPeer *peer = protocols[proto](p_uri);
|
||||
if (!peer) {
|
||||
return;
|
||||
}
|
||||
singleton = memnew(RemoteDebugger(Ref<RemoteDebuggerPeer>(peer)));
|
||||
script_debugger = memnew(ScriptDebugger);
|
||||
// Notify editor of our pid (to allow focus stealing).
|
||||
Array msg = { OS::get_singleton()->get_process_id() };
|
||||
singleton->send_message("set_pid", msg);
|
||||
}
|
||||
if (!singleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a debugger, parse breakpoints.
|
||||
ScriptDebugger *singleton_script_debugger = singleton->get_script_debugger();
|
||||
singleton_script_debugger->set_skip_breakpoints(p_skip_breakpoints);
|
||||
singleton_script_debugger->set_ignore_error_breaks(p_ignore_error_breaks);
|
||||
|
||||
for (int i = 0; i < p_breakpoints.size(); i++) {
|
||||
const String &bp = p_breakpoints[i];
|
||||
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).to_int(), bp.substr(0, sp));
|
||||
}
|
||||
|
||||
allow_focus_steal_fn = p_allow_focus_steal_fn;
|
||||
}
|
||||
|
||||
void EngineDebugger::deinitialize() {
|
||||
if (singleton) {
|
||||
// Stop all profilers
|
||||
for (const KeyValue<StringName, Profiler> &E : profilers) {
|
||||
if (E.value.active) {
|
||||
singleton->profiler_enable(E.key, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining message
|
||||
singleton->poll_events(false);
|
||||
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
// Clear profilers/captures/protocol handlers.
|
||||
profilers.clear();
|
||||
captures.clear();
|
||||
protocols.clear();
|
||||
}
|
||||
|
||||
EngineDebugger::~EngineDebugger() {
|
||||
if (script_debugger) {
|
||||
memdelete(script_debugger);
|
||||
}
|
||||
script_debugger = nullptr;
|
||||
singleton = nullptr;
|
||||
}
|
||||
141
core/debugger/engine_debugger.h
Normal file
141
core/debugger/engine_debugger.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/**************************************************************************/
|
||||
/* engine_debugger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/variant/array.h"
|
||||
|
||||
class RemoteDebuggerPeer;
|
||||
class ScriptDebugger;
|
||||
|
||||
class EngineDebugger {
|
||||
public:
|
||||
typedef void (*ProfilingToggle)(void *p_user, bool p_enable, const Array &p_opts);
|
||||
typedef void (*ProfilingTick)(void *p_user, double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
|
||||
typedef void (*ProfilingAdd)(void *p_user, const Array &p_arr);
|
||||
|
||||
typedef Error (*CaptureFunc)(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
|
||||
|
||||
typedef RemoteDebuggerPeer *(*CreatePeerFunc)(const String &p_uri);
|
||||
|
||||
class Profiler {
|
||||
friend class EngineDebugger;
|
||||
|
||||
ProfilingToggle toggle = nullptr;
|
||||
ProfilingAdd add = nullptr;
|
||||
ProfilingTick tick = nullptr;
|
||||
void *data = nullptr;
|
||||
bool active = false;
|
||||
|
||||
public:
|
||||
Profiler() {}
|
||||
Profiler(void *p_data, ProfilingToggle p_toggle, ProfilingAdd p_add, ProfilingTick p_tick) {
|
||||
data = p_data;
|
||||
toggle = p_toggle;
|
||||
add = p_add;
|
||||
tick = p_tick;
|
||||
}
|
||||
};
|
||||
|
||||
class Capture {
|
||||
friend class EngineDebugger;
|
||||
|
||||
CaptureFunc capture = nullptr;
|
||||
void *data = nullptr;
|
||||
|
||||
public:
|
||||
Capture() {}
|
||||
Capture(void *p_data, CaptureFunc p_capture) {
|
||||
data = p_data;
|
||||
capture = p_capture;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
double frame_time = 0.0;
|
||||
double process_time = 0.0;
|
||||
double physics_time = 0.0;
|
||||
double physics_frame_time = 0.0;
|
||||
|
||||
uint32_t poll_every = 0;
|
||||
|
||||
protected:
|
||||
static inline EngineDebugger *singleton = nullptr;
|
||||
static inline ScriptDebugger *script_debugger = nullptr;
|
||||
|
||||
static inline HashMap<StringName, Profiler> profilers;
|
||||
static inline HashMap<StringName, Capture> captures;
|
||||
static inline HashMap<String, CreatePeerFunc> protocols;
|
||||
|
||||
static void (*allow_focus_steal_fn)();
|
||||
|
||||
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; }
|
||||
|
||||
static void initialize(const String &p_uri, bool p_skip_breakpoints, bool p_ignore_error_breaks, const Vector<String> &p_breakpoints, void (*p_allow_focus_steal_fn)());
|
||||
static void deinitialize();
|
||||
static void register_profiler(const StringName &p_name, const Profiler &p_profiler);
|
||||
static void unregister_profiler(const StringName &p_name);
|
||||
static bool is_profiling(const StringName &p_name);
|
||||
static bool has_profiler(const StringName &p_name);
|
||||
static void profiler_add_frame_data(const StringName &p_name, const Array &p_data);
|
||||
|
||||
static void register_message_capture(const StringName &p_name, Capture p_func);
|
||||
static void unregister_message_capture(const StringName &p_name);
|
||||
static bool has_capture(const StringName &p_name);
|
||||
|
||||
static void register_uri_handler(const String &p_protocol, CreatePeerFunc p_func);
|
||||
|
||||
void iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time);
|
||||
void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
|
||||
Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured);
|
||||
|
||||
void line_poll() {
|
||||
// The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught.
|
||||
if (unlikely(poll_every % 2048) == 0) {
|
||||
poll_events(false);
|
||||
}
|
||||
poll_every++;
|
||||
}
|
||||
|
||||
virtual void poll_events(bool p_is_idle) {}
|
||||
virtual void send_message(const String &p_msg, const Array &p_data) = 0;
|
||||
virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) = 0;
|
||||
virtual void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0;
|
||||
|
||||
virtual ~EngineDebugger();
|
||||
};
|
||||
82
core/debugger/engine_profiler.cpp
Normal file
82
core/debugger/engine_profiler.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/**************************************************************************/
|
||||
/* engine_profiler.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 "engine_profiler.h"
|
||||
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
|
||||
void EngineProfiler::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_toggle, "enable", "options");
|
||||
GDVIRTUAL_BIND(_add_frame, "data");
|
||||
GDVIRTUAL_BIND(_tick, "frame_time", "process_time", "physics_time", "physics_frame_time");
|
||||
}
|
||||
|
||||
void EngineProfiler::toggle(bool p_enable, const Array &p_array) {
|
||||
GDVIRTUAL_CALL(_toggle, p_enable, p_array);
|
||||
}
|
||||
|
||||
void EngineProfiler::add(const Array &p_data) {
|
||||
GDVIRTUAL_CALL(_add_frame, p_data);
|
||||
}
|
||||
|
||||
void EngineProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
|
||||
GDVIRTUAL_CALL(_tick, p_frame_time, p_process_time, p_physics_time, p_physics_frame_time);
|
||||
}
|
||||
|
||||
Error EngineProfiler::bind(const String &p_name) {
|
||||
ERR_FAIL_COND_V(is_bound(), ERR_ALREADY_IN_USE);
|
||||
EngineDebugger::Profiler prof(
|
||||
this,
|
||||
[](void *p_user, bool p_enable, const Array &p_opts) {
|
||||
static_cast<EngineProfiler *>(p_user)->toggle(p_enable, p_opts);
|
||||
},
|
||||
[](void *p_user, const Array &p_data) {
|
||||
static_cast<EngineProfiler *>(p_user)->add(p_data);
|
||||
},
|
||||
[](void *p_user, double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
|
||||
static_cast<EngineProfiler *>(p_user)->tick(p_frame_time, p_process_time, p_physics_time, p_physics_frame_time);
|
||||
});
|
||||
registration = p_name;
|
||||
EngineDebugger::register_profiler(p_name, prof);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EngineProfiler::unbind() {
|
||||
ERR_FAIL_COND_V(!is_bound(), ERR_UNCONFIGURED);
|
||||
EngineDebugger::unregister_profiler(registration);
|
||||
registration.clear();
|
||||
return OK;
|
||||
}
|
||||
|
||||
EngineProfiler::~EngineProfiler() {
|
||||
if (is_bound()) {
|
||||
unbind();
|
||||
}
|
||||
}
|
||||
60
core/debugger/engine_profiler.h
Normal file
60
core/debugger/engine_profiler.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**************************************************************************/
|
||||
/* engine_profiler.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class EngineProfiler : public RefCounted {
|
||||
GDCLASS(EngineProfiler, RefCounted);
|
||||
|
||||
private:
|
||||
String registration;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void toggle(bool p_enable, const Array &p_opts);
|
||||
virtual void add(const Array &p_data);
|
||||
virtual void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
|
||||
|
||||
Error bind(const String &p_name);
|
||||
Error unbind();
|
||||
bool is_bound() const { return registration.length() > 0; }
|
||||
|
||||
GDVIRTUAL2(_toggle, bool, Array);
|
||||
GDVIRTUAL1(_add_frame, Array);
|
||||
GDVIRTUAL4(_tick, double, double, double, double);
|
||||
|
||||
EngineProfiler() {}
|
||||
virtual ~EngineProfiler();
|
||||
};
|
||||
390
core/debugger/local_debugger.cpp
Normal file
390
core/debugger/local_debugger.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
/**************************************************************************/
|
||||
/* local_debugger.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 "local_debugger.h"
|
||||
|
||||
#include "core/debugger/script_debugger.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
struct LocalDebugger::ScriptsProfiler {
|
||||
struct ProfileInfoSort {
|
||||
bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const {
|
||||
return A.total_time > B.total_time;
|
||||
}
|
||||
};
|
||||
|
||||
double frame_time = 0;
|
||||
uint64_t idle_accum = 0;
|
||||
Vector<ScriptLanguage::ProfilingInfo> pinfo;
|
||||
|
||||
void toggle(bool p_enable, const Array &p_opts) {
|
||||
if (p_enable) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->profiling_start();
|
||||
}
|
||||
|
||||
print_line("BEGIN PROFILING");
|
||||
pinfo.resize(32768);
|
||||
} else {
|
||||
_print_frame_data(true);
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->profiling_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
|
||||
frame_time = p_frame_time;
|
||||
_print_frame_data(false);
|
||||
}
|
||||
|
||||
void _print_frame_data(bool p_accumulated) {
|
||||
uint64_t diff = OS::get_singleton()->get_ticks_usec() - idle_accum;
|
||||
|
||||
if (!p_accumulated && diff < 1000000) { //show every one second
|
||||
return;
|
||||
}
|
||||
|
||||
idle_accum = OS::get_singleton()->get_ticks_usec();
|
||||
|
||||
int ofs = 0;
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
if (p_accumulated) {
|
||||
ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&pinfo.write[ofs], pinfo.size() - ofs);
|
||||
} else {
|
||||
ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&pinfo.write[ofs], pinfo.size() - ofs);
|
||||
}
|
||||
}
|
||||
|
||||
SortArray<ScriptLanguage::ProfilingInfo, ProfileInfoSort> sort;
|
||||
sort.sort(pinfo.ptrw(), ofs);
|
||||
|
||||
// compute total script frame time
|
||||
uint64_t script_time_us = 0;
|
||||
for (int i = 0; i < ofs; i++) {
|
||||
script_time_us += pinfo[i].self_time;
|
||||
}
|
||||
double script_time = USEC_TO_SEC(script_time_us);
|
||||
double total_time = p_accumulated ? script_time : frame_time;
|
||||
|
||||
if (!p_accumulated) {
|
||||
print_line("FRAME: total: " + rtos(total_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / total_time) + " %");
|
||||
} else {
|
||||
print_line("ACCUMULATED: total: " + rtos(total_time));
|
||||
}
|
||||
|
||||
for (int i = 0; i < ofs; i++) {
|
||||
print_line(itos(i) + ":" + pinfo[i].signature);
|
||||
double tt = USEC_TO_SEC(pinfo[i].total_time);
|
||||
double st = USEC_TO_SEC(pinfo[i].self_time);
|
||||
print_line("\ttotal: " + rtos(tt) + "/" + itos(tt * 100 / total_time) + " % \tself: " + rtos(st) + "/" + itos(st * 100 / total_time) + " % tcalls: " + itos(pinfo[i].call_count));
|
||||
}
|
||||
}
|
||||
|
||||
ScriptsProfiler() {
|
||||
idle_accum = OS::get_singleton()->get_ticks_usec();
|
||||
}
|
||||
};
|
||||
|
||||
void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
|
||||
ScriptLanguage *script_lang = script_debugger->get_break_language();
|
||||
|
||||
if (!target_function.is_empty()) {
|
||||
String current_function = script_lang->debug_get_stack_level_function(0);
|
||||
if (current_function != target_function) {
|
||||
script_debugger->set_depth(0);
|
||||
script_debugger->set_lines_left(1);
|
||||
return;
|
||||
}
|
||||
target_function = "";
|
||||
}
|
||||
|
||||
print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
|
||||
print_line("*Frame " + itos(0) + " - " + script_lang->debug_get_stack_level_source(0) + ":" + itos(script_lang->debug_get_stack_level_line(0)) + " in function '" + script_lang->debug_get_stack_level_function(0) + "'");
|
||||
print_line("Enter \"help\" for assistance.");
|
||||
int current_frame = 0;
|
||||
int total_frames = script_lang->debug_get_stack_level_count();
|
||||
while (true) {
|
||||
OS::get_singleton()->print("debug> ");
|
||||
String line = OS::get_singleton()->get_stdin_string().strip_edges();
|
||||
|
||||
// Cache options
|
||||
String variable_prefix = options["variable_prefix"];
|
||||
|
||||
if (line.is_empty() && !feof(stdin)) {
|
||||
print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
|
||||
print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
|
||||
print_line("Enter \"help\" for assistance.");
|
||||
} else if (line == "c" || line == "continue") {
|
||||
break;
|
||||
} else if (line == "bt" || line == "breakpoint") {
|
||||
for (int i = 0; i < total_frames; i++) {
|
||||
String cfi = (current_frame == i) ? "*" : " "; //current frame indicator
|
||||
print_line(cfi + "Frame " + itos(i) + " - " + script_lang->debug_get_stack_level_source(i) + ":" + itos(script_lang->debug_get_stack_level_line(i)) + " in function '" + script_lang->debug_get_stack_level_function(i) + "'");
|
||||
}
|
||||
|
||||
} else if (line.begins_with("fr") || line.begins_with("frame")) {
|
||||
if (line.get_slice_count(" ") == 1) {
|
||||
print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
|
||||
} else {
|
||||
int frame = line.get_slicec(' ', 1).to_int();
|
||||
if (frame < 0 || frame >= total_frames) {
|
||||
print_line("Error: Invalid frame.");
|
||||
} else {
|
||||
current_frame = frame;
|
||||
print_line("*Frame " + itos(frame) + " - " + script_lang->debug_get_stack_level_source(frame) + ":" + itos(script_lang->debug_get_stack_level_line(frame)) + " in function '" + script_lang->debug_get_stack_level_function(frame) + "'");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (line.begins_with("set")) {
|
||||
if (line.get_slice_count(" ") == 1) {
|
||||
for (const KeyValue<String, String> &E : options) {
|
||||
print_line("\t" + E.key + "=" + E.value);
|
||||
}
|
||||
|
||||
} else {
|
||||
String key_value = line.get_slicec(' ', 1);
|
||||
int value_pos = key_value.find_char('=');
|
||||
|
||||
if (value_pos < 0) {
|
||||
print_line("Error: Invalid set format. Use: set key=value");
|
||||
} else {
|
||||
String key = key_value.left(value_pos);
|
||||
|
||||
if (!options.has(key)) {
|
||||
print_line("Error: Unknown option " + key);
|
||||
} else {
|
||||
// Allow explicit tab character
|
||||
String value = key_value.substr(value_pos + 1).replace("\\t", "\t");
|
||||
|
||||
options[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (line == "lv" || line == "locals") {
|
||||
List<String> locals;
|
||||
List<Variant> values;
|
||||
script_lang->debug_get_stack_level_locals(current_frame, &locals, &values);
|
||||
print_variables(locals, values, variable_prefix);
|
||||
|
||||
} else if (line == "gv" || line == "globals") {
|
||||
List<String> globals;
|
||||
List<Variant> values;
|
||||
script_lang->debug_get_globals(&globals, &values);
|
||||
print_variables(globals, values, variable_prefix);
|
||||
|
||||
} else if (line == "mv" || line == "members") {
|
||||
List<String> members;
|
||||
List<Variant> values;
|
||||
script_lang->debug_get_stack_level_members(current_frame, &members, &values);
|
||||
print_variables(members, values, variable_prefix);
|
||||
|
||||
} else if (line.begins_with("p") || line.begins_with("print")) {
|
||||
if (line.find_char(' ') < 0) {
|
||||
print_line("Usage: print <expression>");
|
||||
} else {
|
||||
String expr = line.split(" ", true, 1)[1];
|
||||
String res = script_lang->debug_parse_stack_level_expression(current_frame, expr);
|
||||
print_line(res);
|
||||
}
|
||||
|
||||
} else if (line == "s" || line == "step") {
|
||||
script_debugger->set_depth(-1);
|
||||
script_debugger->set_lines_left(1);
|
||||
break;
|
||||
} else if (line == "n" || line == "next") {
|
||||
script_debugger->set_depth(0);
|
||||
script_debugger->set_lines_left(1);
|
||||
break;
|
||||
} else if (line == "fin" || line == "finish") {
|
||||
String current_function = script_lang->debug_get_stack_level_function(0);
|
||||
|
||||
for (int i = 0; i < total_frames; i++) {
|
||||
target_function = script_lang->debug_get_stack_level_function(i);
|
||||
if (target_function != current_function) {
|
||||
script_debugger->set_depth(0);
|
||||
script_debugger->set_lines_left(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
print_line("Error: Reached last frame.");
|
||||
target_function = "";
|
||||
|
||||
} else if (line.begins_with("br") || line.begins_with("break")) {
|
||||
if (line.get_slice_count(" ") <= 1) {
|
||||
const HashMap<int, HashSet<StringName>> &breakpoints = script_debugger->get_breakpoints();
|
||||
if (breakpoints.is_empty()) {
|
||||
print_line("No Breakpoints.");
|
||||
continue;
|
||||
}
|
||||
|
||||
print_line("Breakpoint(s): " + itos(breakpoints.size()));
|
||||
for (const KeyValue<int, HashSet<StringName>> &E : breakpoints) {
|
||||
print_line("\t" + String(*E.value.begin()) + ":" + itos(E.key));
|
||||
}
|
||||
|
||||
} else {
|
||||
Pair<String, int> breakpoint = to_breakpoint(line);
|
||||
|
||||
String source = breakpoint.first;
|
||||
int linenr = breakpoint.second;
|
||||
|
||||
if (source.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
script_debugger->insert_breakpoint(linenr, source);
|
||||
|
||||
print_line("Added breakpoint at " + source + ":" + itos(linenr));
|
||||
}
|
||||
|
||||
} else if (line == "q" || line == "quit" ||
|
||||
(line.is_empty() && feof(stdin))) {
|
||||
// Do not stop again on quit
|
||||
script_debugger->clear_breakpoints();
|
||||
script_debugger->set_depth(-1);
|
||||
script_debugger->set_lines_left(-1);
|
||||
|
||||
MainLoop *main_loop = OS::get_singleton()->get_main_loop();
|
||||
if (main_loop->get_class() == "SceneTree") {
|
||||
main_loop->call("quit");
|
||||
}
|
||||
break;
|
||||
} else if (line.begins_with("delete")) {
|
||||
if (line.get_slice_count(" ") <= 1) {
|
||||
script_debugger->clear_breakpoints();
|
||||
} else {
|
||||
Pair<String, int> breakpoint = to_breakpoint(line);
|
||||
|
||||
String source = breakpoint.first;
|
||||
int linenr = breakpoint.second;
|
||||
|
||||
if (source.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
script_debugger->remove_breakpoint(linenr, source);
|
||||
|
||||
print_line("Removed breakpoint at " + source + ":" + itos(linenr));
|
||||
}
|
||||
|
||||
} else if (line == "h" || line == "help") {
|
||||
print_line("Built-In Debugger command list:\n");
|
||||
print_line("\tc,continue\t\t Continue execution.");
|
||||
print_line("\tbt,backtrace\t\t Show stack trace (frames).");
|
||||
print_line("\tfr,frame <frame>:\t Change current frame.");
|
||||
print_line("\tlv,locals\t\t Show local variables for current frame.");
|
||||
print_line("\tmv,members\t\t Show member variables for \"this\" in frame.");
|
||||
print_line("\tgv,globals\t\t Show global variables.");
|
||||
print_line("\tp,print <expr>\t\t Execute and print variable in expression.");
|
||||
print_line("\ts,step\t\t\t Step to next line.");
|
||||
print_line("\tn,next\t\t\t Next line.");
|
||||
print_line("\tfin,finish\t\t Step out of current frame.");
|
||||
print_line("\tbr,break [source:line]\t List all breakpoints or place a breakpoint.");
|
||||
print_line("\tdelete [source:line]:\t Delete one/all breakpoints.");
|
||||
print_line("\tset [key=value]:\t List all options, or set one.");
|
||||
print_line("\tq,quit\t\t\t Quit application.");
|
||||
} else {
|
||||
print_line("Error: Invalid command, enter \"help\" for assistance.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalDebugger::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) {
|
||||
String value;
|
||||
Vector<String> value_lines;
|
||||
const List<Variant>::Element *V = values.front();
|
||||
for (const String &E : names) {
|
||||
value = String(V->get());
|
||||
|
||||
if (variable_prefix.is_empty()) {
|
||||
print_line(E + ": " + String(V->get()));
|
||||
} else {
|
||||
print_line(E + ":");
|
||||
value_lines = value.split("\n");
|
||||
for (int i = 0; i < value_lines.size(); ++i) {
|
||||
print_line(variable_prefix + value_lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
V = V->next();
|
||||
}
|
||||
}
|
||||
|
||||
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_char(':');
|
||||
if (last_colon < 0) {
|
||||
print_line("Error: Invalid breakpoint format. Expected [source:line]");
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
breakpoint.first = script_debugger->breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges());
|
||||
breakpoint.second = breakpoint_part.substr(last_colon).strip_edges().to_int();
|
||||
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
void LocalDebugger::send_message(const String &p_message, const Array &p_args) {
|
||||
// This needs to be cleaned up entirely.
|
||||
// print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args)));
|
||||
}
|
||||
|
||||
void LocalDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
_err_print_error(p_func.utf8().get_data(), p_file.utf8().get_data(), p_line, p_err, p_descr, p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
LocalDebugger::LocalDebugger() {
|
||||
options["variable_prefix"] = "";
|
||||
|
||||
// Bind scripts profiler.
|
||||
scripts_profiler = memnew(ScriptsProfiler);
|
||||
Profiler scr_prof(
|
||||
scripts_profiler,
|
||||
[](void *p_user, bool p_enable, const Array &p_opts) {
|
||||
static_cast<ScriptsProfiler *>(p_user)->toggle(p_enable, p_opts);
|
||||
},
|
||||
nullptr,
|
||||
[](void *p_user, double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
|
||||
static_cast<ScriptsProfiler *>(p_user)->tick(p_frame_time, p_process_time, p_physics_time, p_physics_frame_time);
|
||||
});
|
||||
register_profiler("scripts", scr_prof);
|
||||
}
|
||||
|
||||
LocalDebugger::~LocalDebugger() {
|
||||
unregister_profiler("scripts");
|
||||
if (scripts_profiler) {
|
||||
memdelete(scripts_profiler);
|
||||
}
|
||||
}
|
||||
56
core/debugger/local_debugger.h
Normal file
56
core/debugger/local_debugger.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**************************************************************************/
|
||||
/* local_debugger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
class LocalDebugger : public EngineDebugger {
|
||||
private:
|
||||
struct ScriptsProfiler;
|
||||
|
||||
ScriptsProfiler *scripts_profiler = nullptr;
|
||||
|
||||
String target_function;
|
||||
HashMap<String, String> options;
|
||||
|
||||
Pair<String, int> to_breakpoint(const String &p_line);
|
||||
void print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix);
|
||||
|
||||
public:
|
||||
void debug(bool p_can_continue, bool p_is_error_breakpoint);
|
||||
void send_message(const String &p_message, const Array &p_args);
|
||||
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
|
||||
LocalDebugger();
|
||||
~LocalDebugger();
|
||||
};
|
||||
772
core/debugger/remote_debugger.cpp
Normal file
772
core/debugger/remote_debugger.cpp
Normal file
@@ -0,0 +1,772 @@
|
||||
/**************************************************************************/
|
||||
/* remote_debugger.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 "remote_debugger.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/debugger/debugger_marshalls.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/debugger/engine_profiler.h"
|
||||
#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"
|
||||
|
||||
class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
|
||||
Object *performance = nullptr;
|
||||
int last_perf_time = 0;
|
||||
uint64_t last_monitor_modification_time = 0;
|
||||
|
||||
public:
|
||||
void toggle(bool p_enable, const Array &p_opts) {}
|
||||
void add(const Array &p_data) {}
|
||||
void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
|
||||
if (!performance) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t pt = OS::get_singleton()->get_ticks_msec();
|
||||
if (pt - last_perf_time < 1000) {
|
||||
return;
|
||||
}
|
||||
last_perf_time = pt;
|
||||
|
||||
Array custom_monitor_names = performance->call("get_custom_monitor_names");
|
||||
|
||||
uint64_t monitor_modification_time = performance->call("get_monitor_modification_time");
|
||||
if (monitor_modification_time > last_monitor_modification_time) {
|
||||
last_monitor_modification_time = monitor_modification_time;
|
||||
EngineDebugger::get_singleton()->send_message("performance:profile_names", custom_monitor_names);
|
||||
}
|
||||
|
||||
int max = performance->get("MONITOR_MAX");
|
||||
Array arr;
|
||||
arr.resize(max + custom_monitor_names.size());
|
||||
for (int i = 0; i < max; i++) {
|
||||
arr[i] = performance->call("get_monitor", i);
|
||||
}
|
||||
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
|
||||
EngineDebugger::get_singleton()->send_message("performance:profile_frame", arr);
|
||||
}
|
||||
|
||||
explicit PerformanceProfiler(Object *p_performance) {
|
||||
performance = p_performance;
|
||||
}
|
||||
};
|
||||
|
||||
Error RemoteDebugger::_put_msg(const String &p_message, const Array &p_data) {
|
||||
Array msg = { p_message, Thread::get_caller_id(), p_data };
|
||||
Error err = peer->put_message(msg);
|
||||
if (err != OK) {
|
||||
n_messages_dropped++;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
RemoteDebugger *rd = static_cast<RemoteDebugger *>(p_this);
|
||||
if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive errors during flush.
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<ScriptLanguage::StackInfo> si;
|
||||
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
si = ScriptServer::get_language(i)->debug_get_current_stack_info();
|
||||
if (si.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// send_error will lock internally.
|
||||
rd->script_debugger->send_error(String::utf8(p_func), String::utf8(p_file), p_line, String::utf8(p_err), String::utf8(p_descr), p_editor_notify, p_type, si);
|
||||
}
|
||||
|
||||
void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) {
|
||||
RemoteDebugger *rd = static_cast<RemoteDebugger *>(p_this);
|
||||
|
||||
if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive prints during flush.
|
||||
return;
|
||||
}
|
||||
|
||||
String s = p_string;
|
||||
int allowed_chars = MIN(MAX(rd->max_chars_per_second - rd->char_count, 0), s.length());
|
||||
|
||||
if (allowed_chars == 0 && s.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (allowed_chars < s.length()) {
|
||||
s = s.substr(0, allowed_chars);
|
||||
}
|
||||
|
||||
MutexLock lock(rd->mutex);
|
||||
|
||||
rd->char_count += allowed_chars;
|
||||
bool overflowed = rd->char_count >= rd->max_chars_per_second;
|
||||
if (rd->is_peer_connected()) {
|
||||
if (overflowed) {
|
||||
s += "[...]";
|
||||
}
|
||||
|
||||
OutputString output_string;
|
||||
output_string.message = s;
|
||||
if (p_error) {
|
||||
output_string.type = MESSAGE_TYPE_ERROR;
|
||||
} else if (p_rich) {
|
||||
output_string.type = MESSAGE_TYPE_LOG_RICH;
|
||||
} else {
|
||||
output_string.type = MESSAGE_TYPE_LOG;
|
||||
}
|
||||
rd->output_strings.push_back(output_string);
|
||||
|
||||
if (overflowed) {
|
||||
output_string.message = "[output overflow, print less text!]";
|
||||
output_string.type = MESSAGE_TYPE_ERROR;
|
||||
rd->output_strings.push_back(output_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String &p_what, const String &p_descr) {
|
||||
ErrorMessage oe;
|
||||
oe.error = p_what;
|
||||
oe.error_descr = p_descr;
|
||||
oe.warning = false;
|
||||
uint64_t time = OS::get_singleton()->get_ticks_msec();
|
||||
oe.hr = time / 3600000;
|
||||
oe.min = (time / 60000) % 60;
|
||||
oe.sec = (time / 1000) % 60;
|
||||
oe.msec = time % 1000;
|
||||
return oe;
|
||||
}
|
||||
|
||||
void RemoteDebugger::flush_output() {
|
||||
MutexLock lock(mutex);
|
||||
flush_thread = Thread::get_caller_id();
|
||||
flushing = true;
|
||||
if (!is_peer_connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (n_messages_dropped > 0) {
|
||||
ErrorMessage err_msg = _create_overflow_error("TOO_MANY_MESSAGES", "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped. Profiling might misbheave, try raising 'network/limits/debugger/max_queued_messages' in project setting.");
|
||||
if (_put_msg("error", err_msg.serialize()) == OK) {
|
||||
n_messages_dropped = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (output_strings.size()) {
|
||||
// Join output strings so we generate less messages.
|
||||
Vector<String> joined_log_strings;
|
||||
Vector<String> strings;
|
||||
Vector<int> types;
|
||||
for (const OutputString &output_string : output_strings) {
|
||||
if (output_string.type == MESSAGE_TYPE_ERROR) {
|
||||
if (!joined_log_strings.is_empty()) {
|
||||
strings.push_back(String("\n").join(joined_log_strings));
|
||||
types.push_back(MESSAGE_TYPE_LOG);
|
||||
joined_log_strings.clear();
|
||||
}
|
||||
strings.push_back(output_string.message);
|
||||
types.push_back(MESSAGE_TYPE_ERROR);
|
||||
} else if (output_string.type == MESSAGE_TYPE_LOG_RICH) {
|
||||
if (!joined_log_strings.is_empty()) {
|
||||
strings.push_back(String("\n").join(joined_log_strings));
|
||||
types.push_back(MESSAGE_TYPE_LOG_RICH);
|
||||
joined_log_strings.clear();
|
||||
}
|
||||
strings.push_back(output_string.message);
|
||||
types.push_back(MESSAGE_TYPE_LOG_RICH);
|
||||
} else {
|
||||
joined_log_strings.push_back(output_string.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!joined_log_strings.is_empty()) {
|
||||
strings.push_back(String("\n").join(joined_log_strings));
|
||||
types.push_back(MESSAGE_TYPE_LOG);
|
||||
}
|
||||
|
||||
Array arr = { strings, types };
|
||||
_put_msg("output", arr);
|
||||
output_strings.clear();
|
||||
}
|
||||
|
||||
while (errors.size()) {
|
||||
ErrorMessage oe = errors.front()->get();
|
||||
_put_msg("error", oe.serialize());
|
||||
errors.pop_front();
|
||||
}
|
||||
|
||||
// Update limits
|
||||
uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000;
|
||||
|
||||
if (ticks - last_reset > 1000) {
|
||||
last_reset = ticks;
|
||||
char_count = 0;
|
||||
err_count = 0;
|
||||
n_errors_dropped = 0;
|
||||
warn_count = 0;
|
||||
n_warnings_dropped = 0;
|
||||
}
|
||||
flushing = false;
|
||||
}
|
||||
|
||||
void RemoteDebugger::send_message(const String &p_message, const Array &p_args) {
|
||||
MutexLock lock(mutex);
|
||||
if (is_peer_connected()) {
|
||||
_put_msg(p_message, p_args);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
ErrorMessage oe;
|
||||
oe.error = p_err;
|
||||
oe.error_descr = p_descr;
|
||||
oe.source_file = p_file;
|
||||
oe.source_line = p_line;
|
||||
oe.source_func = p_func;
|
||||
oe.warning = p_type == ERR_HANDLER_WARNING;
|
||||
uint64_t time = OS::get_singleton()->get_ticks_msec();
|
||||
oe.hr = time / 3600000;
|
||||
oe.min = (time / 60000) % 60;
|
||||
oe.sec = (time / 1000) % 60;
|
||||
oe.msec = time % 1000;
|
||||
oe.callstack.append_array(script_debugger->get_error_stack_info());
|
||||
|
||||
if (flushing && Thread::get_caller_id() == flush_thread) { // Can't handle recursive errors during flush.
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock lock(mutex);
|
||||
|
||||
if (oe.warning) {
|
||||
warn_count++;
|
||||
} else {
|
||||
err_count++;
|
||||
}
|
||||
|
||||
if (is_peer_connected()) {
|
||||
if (oe.warning) {
|
||||
if (warn_count > max_warnings_per_second) {
|
||||
n_warnings_dropped++;
|
||||
if (n_warnings_dropped == 1) {
|
||||
// Only print one message about dropping per second
|
||||
ErrorMessage overflow = _create_overflow_error("TOO_MANY_WARNINGS", "Too many warnings! Ignoring warnings for up to 1 second.");
|
||||
errors.push_back(overflow);
|
||||
}
|
||||
} else {
|
||||
errors.push_back(oe);
|
||||
}
|
||||
} else {
|
||||
if (err_count > max_errors_per_second) {
|
||||
n_errors_dropped++;
|
||||
if (n_errors_dropped == 1) {
|
||||
// Only print one message about dropping per second
|
||||
ErrorMessage overflow = _create_overflow_error("TOO_MANY_ERRORS", "Too many errors! Ignoring errors for up to 1 second.");
|
||||
errors.push_back(overflow);
|
||||
}
|
||||
} else {
|
||||
errors.push_back(oe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteDebugger::_send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type) {
|
||||
DebuggerMarshalls::ScriptStackVariable stvar;
|
||||
List<String>::Element *E = p_names.front();
|
||||
List<Variant>::Element *F = p_vals.front();
|
||||
while (E) {
|
||||
stvar.name = E->get();
|
||||
stvar.value = F->get();
|
||||
stvar.type = p_type;
|
||||
send_message("stack_frame_var", stvar.serialize());
|
||||
E = E->next();
|
||||
F = F->next();
|
||||
}
|
||||
}
|
||||
|
||||
Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, bool &r_captured) {
|
||||
const int idx = p_msg.find_char(':');
|
||||
r_captured = false;
|
||||
if (idx < 0) { // No prefix, unknown message.
|
||||
return OK;
|
||||
}
|
||||
const String cap = p_msg.substr(0, idx);
|
||||
if (!has_capture(cap)) {
|
||||
return ERR_UNAVAILABLE; // Unknown message...
|
||||
}
|
||||
const String msg = p_msg.substr(idx + 1);
|
||||
return capture_parse(cap, msg, p_data, r_captured);
|
||||
}
|
||||
|
||||
void RemoteDebugger::_poll_messages() {
|
||||
MutexLock mutex_lock(mutex);
|
||||
|
||||
peer->poll();
|
||||
while (peer->has_message()) {
|
||||
Array cmd = peer->get_message();
|
||||
ERR_CONTINUE(cmd.size() != 3);
|
||||
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
|
||||
ERR_CONTINUE(cmd[1].get_type() != Variant::INT);
|
||||
ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY);
|
||||
|
||||
Thread::ID thread = cmd[1];
|
||||
|
||||
if (!messages.has(thread)) {
|
||||
continue; // This thread is not around to receive the messages
|
||||
}
|
||||
|
||||
Message msg;
|
||||
msg.message = cmd[0];
|
||||
msg.data = cmd[2];
|
||||
messages[thread].push_back(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteDebugger::_has_messages() {
|
||||
MutexLock mutex_lock(mutex);
|
||||
return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty();
|
||||
}
|
||||
|
||||
Array RemoteDebugger::_get_message() {
|
||||
MutexLock mutex_lock(mutex);
|
||||
ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array());
|
||||
List<Message> &message_list = messages[Thread::get_caller_id()];
|
||||
ERR_FAIL_COND_V(message_list.is_empty(), Array());
|
||||
|
||||
Array msg;
|
||||
msg.resize(2);
|
||||
msg[0] = message_list.front()->get().message;
|
||||
msg[1] = message_list.front()->get().data;
|
||||
message_list.pop_front();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
|
||||
//this function is called when there is a debugger break (bug on script)
|
||||
//or when execution is paused from editor
|
||||
|
||||
{
|
||||
MutexLock lock(mutex);
|
||||
// Tests that require mutex.
|
||||
if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway.");
|
||||
|
||||
if (!peer->can_block()) {
|
||||
return; // Peer does not support blocking IO. We could at least send the error though.
|
||||
}
|
||||
}
|
||||
|
||||
if (p_is_error_breakpoint && script_debugger->is_ignoring_error_breaks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScriptLanguage *script_lang = script_debugger->get_break_language();
|
||||
ERR_FAIL_NULL(script_lang);
|
||||
|
||||
Array msg = {
|
||||
p_can_continue,
|
||||
script_lang->debug_get_error(),
|
||||
script_lang->debug_get_stack_level_count() > 0,
|
||||
Thread::get_caller_id()
|
||||
};
|
||||
if (allow_focus_steal_fn) {
|
||||
allow_focus_steal_fn();
|
||||
}
|
||||
send_message("debug_enter", msg);
|
||||
|
||||
Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE;
|
||||
|
||||
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||
mouse_mode = Input::get_singleton()->get_mouse_mode();
|
||||
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
|
||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
|
||||
}
|
||||
} else {
|
||||
MutexLock mutex_lock(mutex);
|
||||
messages.insert(Thread::get_caller_id(), List<Message>());
|
||||
}
|
||||
|
||||
while (is_peer_connected()) {
|
||||
flush_output();
|
||||
|
||||
_poll_messages();
|
||||
|
||||
if (_has_messages()) {
|
||||
Array cmd = _get_message();
|
||||
|
||||
ERR_CONTINUE(cmd.size() != 2);
|
||||
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
|
||||
ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY);
|
||||
|
||||
String command = cmd[0];
|
||||
Array data = cmd[1];
|
||||
|
||||
if (command == "step") {
|
||||
script_debugger->set_depth(-1);
|
||||
script_debugger->set_lines_left(1);
|
||||
break;
|
||||
|
||||
} else if (command == "next") {
|
||||
script_debugger->set_depth(0);
|
||||
script_debugger->set_lines_left(1);
|
||||
break;
|
||||
|
||||
} else if (command == "continue") {
|
||||
script_debugger->set_depth(-1);
|
||||
script_debugger->set_lines_left(-1);
|
||||
break;
|
||||
|
||||
} else if (command == "break") {
|
||||
ERR_PRINT("Got break when already broke!");
|
||||
break;
|
||||
|
||||
} else if (command == "get_stack_dump") {
|
||||
DebuggerMarshalls::ScriptStackDump dump;
|
||||
int slc = script_lang->debug_get_stack_level_count();
|
||||
for (int i = 0; i < slc; i++) {
|
||||
ScriptLanguage::StackInfo frame;
|
||||
frame.file = script_lang->debug_get_stack_level_source(i);
|
||||
frame.line = script_lang->debug_get_stack_level_line(i);
|
||||
frame.func = script_lang->debug_get_stack_level_function(i);
|
||||
dump.frames.push_back(frame);
|
||||
}
|
||||
send_message("stack_dump", dump.serialize());
|
||||
|
||||
} else if (command == "get_stack_frame_vars") {
|
||||
ERR_FAIL_COND(data.size() != 1);
|
||||
ERR_FAIL_NULL(script_lang);
|
||||
int lv = data[0];
|
||||
|
||||
List<String> members;
|
||||
List<Variant> member_vals;
|
||||
if (ScriptInstance *inst = script_lang->debug_get_stack_level_instance(lv)) {
|
||||
members.push_back("self");
|
||||
member_vals.push_back(inst->get_owner());
|
||||
}
|
||||
script_lang->debug_get_stack_level_members(lv, &members, &member_vals);
|
||||
ERR_FAIL_COND(members.size() != member_vals.size());
|
||||
|
||||
List<String> locals;
|
||||
List<Variant> local_vals;
|
||||
script_lang->debug_get_stack_level_locals(lv, &locals, &local_vals);
|
||||
ERR_FAIL_COND(locals.size() != local_vals.size());
|
||||
|
||||
List<String> globals;
|
||||
List<Variant> globals_vals;
|
||||
script_lang->debug_get_globals(&globals, &globals_vals);
|
||||
ERR_FAIL_COND(globals.size() != globals_vals.size());
|
||||
|
||||
Array var_size = { local_vals.size() + member_vals.size() + globals_vals.size() };
|
||||
send_message("stack_frame_vars", var_size);
|
||||
_send_stack_vars(locals, local_vals, 0);
|
||||
_send_stack_vars(members, member_vals, 1);
|
||||
_send_stack_vars(globals, globals_vals, 2);
|
||||
|
||||
} else if (command == "reload_scripts") {
|
||||
script_paths_to_reload = data;
|
||||
} else if (command == "reload_all_scripts") {
|
||||
reload_all_scripts = true;
|
||||
} else if (command == "breakpoint") {
|
||||
ERR_FAIL_COND(data.size() < 3);
|
||||
bool set = data[2];
|
||||
if (set) {
|
||||
script_debugger->insert_breakpoint(data[1], data[0]);
|
||||
} else {
|
||||
script_debugger->remove_breakpoint(data[1], data[0]);
|
||||
}
|
||||
|
||||
} else if (command == "set_skip_breakpoints") {
|
||||
ERR_FAIL_COND(data.is_empty());
|
||||
script_debugger->set_skip_breakpoints(data[0]);
|
||||
} else if (command == "set_ignore_error_breaks") {
|
||||
ERR_FAIL_COND(data.is_empty());
|
||||
script_debugger->set_ignore_error_breaks(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;
|
||||
}
|
||||
|
||||
PackedStringArray input_names;
|
||||
Array input_vals;
|
||||
|
||||
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());
|
||||
|
||||
for (const String &S : locals) {
|
||||
input_names.append(S);
|
||||
}
|
||||
|
||||
for (const Variant &V : local_vals) {
|
||||
input_vals.append(V);
|
||||
}
|
||||
|
||||
List<String> globals;
|
||||
List<Variant> globals_vals;
|
||||
script_debugger->get_break_language()->debug_get_globals(&globals, &globals_vals);
|
||||
ERR_FAIL_COND(globals.size() != globals_vals.size());
|
||||
|
||||
for (const String &S : globals) {
|
||||
input_names.append(S);
|
||||
}
|
||||
|
||||
for (const Variant &V : globals_vals) {
|
||||
input_vals.append(V);
|
||||
}
|
||||
|
||||
List<StringName> native_types;
|
||||
ClassDB::get_class_list(&native_types);
|
||||
for (const StringName &E : native_types) {
|
||||
if (!ClassDB::is_class_exposed(E) || !Engine::get_singleton()->has_singleton(E) || Engine::get_singleton()->is_singleton_editor_only(E)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
input_names.append(E);
|
||||
input_vals.append(Engine::get_singleton()->get_singleton_object(E));
|
||||
}
|
||||
|
||||
List<StringName> user_types;
|
||||
ScriptServer::get_global_class_list(&user_types);
|
||||
for (const StringName &S : user_types) {
|
||||
String scr_path = ScriptServer::get_global_class_path(S);
|
||||
Ref<Script> scr = ResourceLoader::load(scr_path, "Script");
|
||||
ERR_CONTINUE_MSG(scr.is_null(), vformat(R"(Could not load the global class %s from resource path: "%s".)", S, scr_path));
|
||||
|
||||
input_names.append(S);
|
||||
input_vals.append(scr);
|
||||
}
|
||||
|
||||
Expression expression;
|
||||
expression.parse(expression_str, input_names);
|
||||
const Variant return_val = expression.execute(input_vals, 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(vformat("Unknown message received from debugger: %s.", command));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OS::get_singleton()->delay_usec(10000);
|
||||
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||
// If this is a busy loop on the main thread, events still need to be processed.
|
||||
DisplayServer::get_singleton()->force_process_and_drop_events();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_message("debug_exit", Array());
|
||||
|
||||
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
|
||||
Input::get_singleton()->set_mouse_mode(mouse_mode);
|
||||
}
|
||||
} else {
|
||||
MutexLock mutex_lock(mutex);
|
||||
messages.erase(Thread::get_caller_id());
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteDebugger::poll_events(bool p_is_idle) {
|
||||
if (peer.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush_output();
|
||||
|
||||
_poll_messages();
|
||||
|
||||
while (_has_messages()) {
|
||||
Array arr = _get_message();
|
||||
|
||||
ERR_CONTINUE(arr.size() != 2);
|
||||
ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
|
||||
ERR_CONTINUE(arr[1].get_type() != Variant::ARRAY);
|
||||
|
||||
const String cmd = arr[0];
|
||||
const int idx = cmd.find_char(':');
|
||||
bool parsed = false;
|
||||
if (idx < 0) { // Not prefix, use scripts capture.
|
||||
capture_parse("core", cmd, arr[1], parsed);
|
||||
continue;
|
||||
}
|
||||
|
||||
const String cap = cmd.substr(0, idx);
|
||||
if (!has_capture(cap)) {
|
||||
continue; // Unknown message...
|
||||
}
|
||||
|
||||
const String msg = cmd.substr(idx + 1);
|
||||
capture_parse(cap, msg, arr[1], parsed);
|
||||
}
|
||||
|
||||
// Reload scripts during idle poll only.
|
||||
if (p_is_idle) {
|
||||
if (reload_all_scripts) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->reload_all_scripts();
|
||||
}
|
||||
reload_all_scripts = false;
|
||||
} else if (!script_paths_to_reload.is_empty()) {
|
||||
Array scripts_to_reload;
|
||||
for (int i = 0; i < script_paths_to_reload.size(); ++i) {
|
||||
String path = script_paths_to_reload[i];
|
||||
Error err = OK;
|
||||
Ref<Script> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
|
||||
ERR_CONTINUE_MSG(err != OK, vformat("Could not reload script '%s': %s", path, error_names[err]));
|
||||
ERR_CONTINUE_MSG(script.is_null(), vformat("Could not reload script '%s': Not a script!", path, error_names[err]));
|
||||
scripts_to_reload.push_back(script);
|
||||
}
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->reload_scripts(scripts_to_reload, true);
|
||||
}
|
||||
}
|
||||
script_paths_to_reload.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
|
||||
r_captured = true;
|
||||
if (p_cmd == "reload_scripts") {
|
||||
script_paths_to_reload = p_data;
|
||||
} else if (p_cmd == "reload_all_scripts") {
|
||||
reload_all_scripts = true;
|
||||
} else if (p_cmd == "breakpoint") {
|
||||
ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA);
|
||||
bool set = p_data[2];
|
||||
if (set) {
|
||||
script_debugger->insert_breakpoint(p_data[1], p_data[0]);
|
||||
} else {
|
||||
script_debugger->remove_breakpoint(p_data[1], p_data[0]);
|
||||
}
|
||||
|
||||
} else if (p_cmd == "set_skip_breakpoints") {
|
||||
ERR_FAIL_COND_V(p_data.is_empty(), ERR_INVALID_DATA);
|
||||
script_debugger->set_skip_breakpoints(p_data[0]);
|
||||
} else if (p_cmd == "set_ignore_error_breaks") {
|
||||
ERR_FAIL_COND_V(p_data.is_empty(), ERR_INVALID_DATA);
|
||||
script_debugger->set_ignore_error_breaks(p_data[0]);
|
||||
} else if (p_cmd == "break") {
|
||||
script_debugger->debug(script_debugger->get_break_language());
|
||||
} else {
|
||||
r_captured = false;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error RemoteDebugger::_profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
|
||||
r_captured = false;
|
||||
ERR_FAIL_COND_V(p_data.is_empty(), ERR_INVALID_DATA);
|
||||
ERR_FAIL_COND_V(p_data[0].get_type() != Variant::BOOL, ERR_INVALID_DATA);
|
||||
ERR_FAIL_COND_V(!has_profiler(p_cmd), ERR_UNAVAILABLE);
|
||||
Array opts;
|
||||
if (p_data.size() > 1) { // Optional profiler parameters.
|
||||
ERR_FAIL_COND_V(p_data[1].get_type() != Variant::ARRAY, ERR_INVALID_DATA);
|
||||
opts = p_data[1];
|
||||
}
|
||||
r_captured = true;
|
||||
profiler_enable(p_cmd, p_data[0], opts);
|
||||
return OK;
|
||||
}
|
||||
|
||||
RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
|
||||
peer = p_peer;
|
||||
max_chars_per_second = GLOBAL_GET("network/limits/debugger/max_chars_per_second");
|
||||
max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");
|
||||
max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second");
|
||||
|
||||
// Performance Profiler
|
||||
Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
|
||||
if (perf) {
|
||||
performance_profiler.instantiate(perf);
|
||||
performance_profiler->bind("performance");
|
||||
profiler_enable("performance", true);
|
||||
}
|
||||
|
||||
// Core and profiler captures.
|
||||
Capture core_cap(this,
|
||||
[](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
|
||||
return static_cast<RemoteDebugger *>(p_user)->_core_capture(p_cmd, p_data, r_captured);
|
||||
});
|
||||
register_message_capture("core", core_cap);
|
||||
Capture profiler_cap(this,
|
||||
[](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
|
||||
return static_cast<RemoteDebugger *>(p_user)->_profiler_capture(p_cmd, p_data, r_captured);
|
||||
});
|
||||
register_message_capture("profiler", profiler_cap);
|
||||
|
||||
// Error handlers
|
||||
phl.printfunc = _print_handler;
|
||||
phl.userdata = this;
|
||||
add_print_handler(&phl);
|
||||
|
||||
eh.errfunc = _err_handler;
|
||||
eh.userdata = this;
|
||||
add_error_handler(&eh);
|
||||
|
||||
messages.insert(Thread::get_main_id(), List<Message>());
|
||||
}
|
||||
|
||||
RemoteDebugger::~RemoteDebugger() {
|
||||
remove_print_handler(&phl);
|
||||
remove_error_handler(&eh);
|
||||
}
|
||||
123
core/debugger/remote_debugger.h
Normal file
123
core/debugger/remote_debugger.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/**************************************************************************/
|
||||
/* remote_debugger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/debugger/debugger_marshalls.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/debugger/remote_debugger_peer.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/variant/array.h"
|
||||
|
||||
class RemoteDebugger : public EngineDebugger {
|
||||
public:
|
||||
enum MessageType {
|
||||
MESSAGE_TYPE_LOG,
|
||||
MESSAGE_TYPE_ERROR,
|
||||
MESSAGE_TYPE_LOG_RICH,
|
||||
};
|
||||
|
||||
private:
|
||||
typedef DebuggerMarshalls::OutputError ErrorMessage;
|
||||
|
||||
class PerformanceProfiler;
|
||||
|
||||
Ref<PerformanceProfiler> performance_profiler;
|
||||
|
||||
Ref<RemoteDebuggerPeer> peer;
|
||||
|
||||
struct OutputString {
|
||||
String message;
|
||||
MessageType type;
|
||||
};
|
||||
List<OutputString> output_strings;
|
||||
List<ErrorMessage> errors;
|
||||
|
||||
int n_messages_dropped = 0;
|
||||
int max_errors_per_second = 0;
|
||||
int max_chars_per_second = 0;
|
||||
int max_warnings_per_second = 0;
|
||||
int n_errors_dropped = 0;
|
||||
int n_warnings_dropped = 0;
|
||||
int char_count = 0;
|
||||
int err_count = 0;
|
||||
int warn_count = 0;
|
||||
int last_reset = 0;
|
||||
bool reload_all_scripts = false;
|
||||
Array script_paths_to_reload;
|
||||
|
||||
// Make handlers and send_message thread safe.
|
||||
Mutex mutex;
|
||||
bool flushing = false;
|
||||
Thread::ID flush_thread = 0;
|
||||
|
||||
struct Message {
|
||||
String message;
|
||||
Array data;
|
||||
};
|
||||
|
||||
HashMap<Thread::ID, List<Message>> messages;
|
||||
|
||||
void _poll_messages();
|
||||
bool _has_messages();
|
||||
Array _get_message();
|
||||
|
||||
PrintHandlerList phl;
|
||||
static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich);
|
||||
ErrorHandlerList eh;
|
||||
static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
|
||||
ErrorMessage _create_overflow_error(const String &p_what, const String &p_descr);
|
||||
Error _put_msg(const String &p_message, const Array &p_data);
|
||||
|
||||
bool is_peer_connected() { return peer->is_peer_connected(); }
|
||||
void flush_output();
|
||||
|
||||
void _send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type);
|
||||
|
||||
Error _profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
|
||||
Error _core_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
|
||||
|
||||
template <typename T>
|
||||
void _bind_profiler(const String &p_name, T *p_prof);
|
||||
Error _try_capture(const String &p_name, const Array &p_data, bool &r_captured);
|
||||
|
||||
public:
|
||||
// Overrides
|
||||
void poll_events(bool p_is_idle);
|
||||
void send_message(const String &p_message, const Array &p_args);
|
||||
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false);
|
||||
|
||||
explicit RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer);
|
||||
~RemoteDebugger();
|
||||
};
|
||||
248
core/debugger/remote_debugger_peer.cpp
Normal file
248
core/debugger/remote_debugger_peer.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/**************************************************************************/
|
||||
/* remote_debugger_peer.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 "remote_debugger_peer.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
bool RemoteDebuggerPeerTCP::is_peer_connected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
bool RemoteDebuggerPeerTCP::has_message() {
|
||||
return in_queue.size() > 0;
|
||||
}
|
||||
|
||||
Array RemoteDebuggerPeerTCP::get_message() {
|
||||
MutexLock lock(mutex);
|
||||
List<Array>::Element *E = in_queue.front();
|
||||
ERR_FAIL_NULL_V_MSG(E, Array(), "No remote debugger messages in queue.");
|
||||
|
||||
Array out = E->get();
|
||||
in_queue.pop_front();
|
||||
return out;
|
||||
}
|
||||
|
||||
Error RemoteDebuggerPeerTCP::put_message(const Array &p_arr) {
|
||||
MutexLock lock(mutex);
|
||||
if (out_queue.size() >= max_queued_messages) {
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
out_queue.push_back(p_arr);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int RemoteDebuggerPeerTCP::get_max_message_size() const {
|
||||
return 8 << 20; // 8 MiB
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::close() {
|
||||
running = false;
|
||||
if (thread.is_started()) {
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
tcp_client->disconnect_from_host();
|
||||
out_buf.clear();
|
||||
in_buf.clear();
|
||||
}
|
||||
|
||||
RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) {
|
||||
// This means remote debugger takes 16 MiB just because it exists...
|
||||
in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size).
|
||||
out_buf.resize(8 << 20); // 8 MiB should be way more than enough
|
||||
tcp_client = p_tcp;
|
||||
if (tcp_client.is_valid()) { // Attaching to an already connected stream.
|
||||
connected = true;
|
||||
running = true;
|
||||
thread.start(_thread_func, this);
|
||||
} else {
|
||||
tcp_client.instantiate();
|
||||
}
|
||||
}
|
||||
|
||||
RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() {
|
||||
close();
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::_write_out() {
|
||||
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED && tcp_client->wait(NetSocket::POLL_TYPE_OUT) == OK) {
|
||||
uint8_t *buf = out_buf.ptrw();
|
||||
if (out_left <= 0) {
|
||||
mutex.lock();
|
||||
List<Array>::Element *E = out_queue.front();
|
||||
if (!E) {
|
||||
mutex.unlock();
|
||||
break;
|
||||
}
|
||||
Variant var = E->get();
|
||||
out_queue.pop_front();
|
||||
mutex.unlock();
|
||||
int size = 0;
|
||||
Error err = encode_variant(var, nullptr, size);
|
||||
ERR_CONTINUE(err != OK || size > out_buf.size() - 4); // 4 bytes separator.
|
||||
encode_uint32(size, buf);
|
||||
encode_variant(var, buf + 4, size);
|
||||
out_left = size + 4;
|
||||
out_pos = 0;
|
||||
}
|
||||
int sent = 0;
|
||||
tcp_client->put_partial_data(buf + out_pos, out_left, sent);
|
||||
out_left -= sent;
|
||||
out_pos += sent;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::_read_in() {
|
||||
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED && tcp_client->wait(NetSocket::POLL_TYPE_IN) == OK) {
|
||||
uint8_t *buf = in_buf.ptrw();
|
||||
if (in_left <= 0) {
|
||||
if (in_queue.size() > max_queued_messages) {
|
||||
break; // Too many messages already in queue.
|
||||
}
|
||||
if (tcp_client->get_available_bytes() < 4) {
|
||||
break; // Need 4 more bytes.
|
||||
}
|
||||
uint32_t size = 0;
|
||||
int read = 0;
|
||||
Error err = tcp_client->get_partial_data((uint8_t *)&size, 4, read);
|
||||
ERR_CONTINUE(read != 4 || err != OK || size > (uint32_t)in_buf.size());
|
||||
in_left = size;
|
||||
in_pos = 0;
|
||||
}
|
||||
int read = 0;
|
||||
tcp_client->get_partial_data(buf + in_pos, in_left, read);
|
||||
in_left -= read;
|
||||
in_pos += read;
|
||||
if (in_left == 0) {
|
||||
Variant var;
|
||||
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.");
|
||||
MutexLock lock(mutex);
|
||||
in_queue.push_back(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) {
|
||||
IPAddress ip;
|
||||
if (p_host.is_valid_ip_address()) {
|
||||
ip = p_host;
|
||||
} else {
|
||||
ip = IP::get_singleton()->resolve_hostname(p_host);
|
||||
}
|
||||
|
||||
int port = p_port;
|
||||
|
||||
const int tries = 6;
|
||||
const int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 };
|
||||
|
||||
Error err = tcp_client->connect_to_host(ip, port);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Remote Debugger: Unable to connect to host '%s:%d'.", p_host, port));
|
||||
|
||||
for (int i = 0; i < tries; i++) {
|
||||
tcp_client->poll();
|
||||
if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
|
||||
print_verbose("Remote Debugger: Connected!");
|
||||
break;
|
||||
} else {
|
||||
const int ms = waits[i];
|
||||
OS::get_singleton()->delay_usec(ms * 1000);
|
||||
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(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num_int64(tcp_client->get_status())));
|
||||
return FAILED;
|
||||
}
|
||||
connected = true;
|
||||
running = true;
|
||||
thread.start(_thread_func, this);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) {
|
||||
// Update in time for 144hz monitors
|
||||
const uint64_t min_tick = 6900;
|
||||
RemoteDebuggerPeerTCP *peer = static_cast<RemoteDebuggerPeerTCP *>(p_ud);
|
||||
while (peer->running && peer->is_peer_connected()) {
|
||||
uint64_t ticks_usec = OS::get_singleton()->get_ticks_usec();
|
||||
peer->_poll();
|
||||
if (!peer->is_peer_connected()) {
|
||||
break;
|
||||
}
|
||||
ticks_usec = OS::get_singleton()->get_ticks_usec() - ticks_usec;
|
||||
if (ticks_usec < min_tick) {
|
||||
OS::get_singleton()->delay_usec(min_tick - ticks_usec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::poll() {
|
||||
// Nothing to do, polling is done in thread.
|
||||
}
|
||||
|
||||
void RemoteDebuggerPeerTCP::_poll() {
|
||||
tcp_client->poll();
|
||||
if (connected) {
|
||||
_write_out();
|
||||
_read_in();
|
||||
connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) {
|
||||
ERR_FAIL_COND_V(!p_uri.begins_with("tcp://"), nullptr);
|
||||
|
||||
String debug_host = p_uri.replace("tcp://", "");
|
||||
uint16_t debug_port = 6007;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
RemoteDebuggerPeerTCP *peer = memnew(RemoteDebuggerPeerTCP);
|
||||
Error err = peer->connect_to_host(debug_host, debug_port);
|
||||
if (err != OK) {
|
||||
memdelete(peer);
|
||||
return nullptr;
|
||||
}
|
||||
return peer;
|
||||
}
|
||||
|
||||
RemoteDebuggerPeer::RemoteDebuggerPeer() {
|
||||
max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages");
|
||||
}
|
||||
93
core/debugger/remote_debugger_peer.h
Normal file
93
core/debugger/remote_debugger_peer.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/**************************************************************************/
|
||||
/* remote_debugger_peer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class RemoteDebuggerPeer : public RefCounted {
|
||||
protected:
|
||||
int max_queued_messages = 4096;
|
||||
|
||||
public:
|
||||
virtual bool is_peer_connected() = 0;
|
||||
virtual int get_max_message_size() const = 0;
|
||||
virtual bool has_message() = 0;
|
||||
virtual Error put_message(const Array &p_arr) = 0;
|
||||
virtual Array get_message() = 0;
|
||||
virtual void close() = 0;
|
||||
virtual void poll() = 0;
|
||||
virtual bool can_block() const { return true; } // If blocking io is allowed on main thread (debug).
|
||||
|
||||
RemoteDebuggerPeer();
|
||||
};
|
||||
|
||||
class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer {
|
||||
private:
|
||||
Ref<StreamPeerTCP> tcp_client;
|
||||
Mutex mutex;
|
||||
Thread thread;
|
||||
List<Array> in_queue;
|
||||
List<Array> out_queue;
|
||||
int out_left = 0;
|
||||
int out_pos = 0;
|
||||
Vector<uint8_t> out_buf;
|
||||
int in_left = 0;
|
||||
int in_pos = 0;
|
||||
Vector<uint8_t> in_buf;
|
||||
bool connected = false;
|
||||
bool running = false;
|
||||
|
||||
static void _thread_func(void *p_ud);
|
||||
|
||||
void _poll();
|
||||
void _write_out();
|
||||
void _read_in();
|
||||
|
||||
public:
|
||||
static RemoteDebuggerPeer *create(const String &p_uri);
|
||||
|
||||
Error connect_to_host(const String &p_host, uint16_t p_port);
|
||||
|
||||
bool is_peer_connected() override;
|
||||
int get_max_message_size() const override;
|
||||
bool has_message() override;
|
||||
Error put_message(const Array &p_arr) override;
|
||||
Array get_message() override;
|
||||
void poll() override;
|
||||
void close() override;
|
||||
|
||||
RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_stream = Ref<StreamPeerTCP>());
|
||||
~RemoteDebuggerPeerTCP();
|
||||
};
|
||||
107
core/debugger/script_debugger.cpp
Normal file
107
core/debugger/script_debugger.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/**************************************************************************/
|
||||
/* script_debugger.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 "script_debugger.h"
|
||||
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
|
||||
thread_local Vector<ScriptDebugger::StackInfo> ScriptDebugger::error_stack_info;
|
||||
|
||||
void ScriptDebugger::set_lines_left(int p_left) {
|
||||
lines_left = p_left;
|
||||
}
|
||||
|
||||
void ScriptDebugger::set_depth(int p_depth) {
|
||||
depth = p_depth;
|
||||
}
|
||||
|
||||
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
|
||||
if (!breakpoints.has(p_line)) {
|
||||
breakpoints[p_line] = HashSet<StringName>();
|
||||
}
|
||||
breakpoints[p_line].insert(p_source);
|
||||
}
|
||||
|
||||
void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
|
||||
if (!breakpoints.has(p_line)) {
|
||||
return;
|
||||
}
|
||||
|
||||
breakpoints[p_line].erase(p_source);
|
||||
if (breakpoints[p_line].is_empty()) {
|
||||
breakpoints.erase(p_line);
|
||||
}
|
||||
}
|
||||
|
||||
String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
|
||||
return p_source;
|
||||
}
|
||||
|
||||
void ScriptDebugger::clear_breakpoints() {
|
||||
breakpoints.clear();
|
||||
}
|
||||
|
||||
void ScriptDebugger::set_skip_breakpoints(bool p_skip_breakpoints) {
|
||||
skip_breakpoints = p_skip_breakpoints;
|
||||
}
|
||||
|
||||
bool ScriptDebugger::is_skipping_breakpoints() {
|
||||
return skip_breakpoints;
|
||||
}
|
||||
|
||||
void ScriptDebugger::set_ignore_error_breaks(bool p_ignore) {
|
||||
ignore_error_breaks = p_ignore;
|
||||
}
|
||||
|
||||
bool ScriptDebugger::is_ignoring_error_breaks() {
|
||||
return ignore_error_breaks;
|
||||
}
|
||||
|
||||
void ScriptDebugger::debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) {
|
||||
ScriptLanguage *prev = break_lang;
|
||||
break_lang = p_lang;
|
||||
EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint);
|
||||
break_lang = prev;
|
||||
}
|
||||
|
||||
void ScriptDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info) {
|
||||
// Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way.
|
||||
error_stack_info.append_array(p_stack_info);
|
||||
EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type);
|
||||
error_stack_info.clear(); // Clear because this is thread local
|
||||
}
|
||||
|
||||
Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {
|
||||
return error_stack_info;
|
||||
}
|
||||
|
||||
ScriptLanguage *ScriptDebugger::get_break_language() const {
|
||||
return break_lang;
|
||||
}
|
||||
86
core/debugger/script_debugger.h
Normal file
86
core/debugger/script_debugger.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**************************************************************************/
|
||||
/* script_debugger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/vector.h"
|
||||
|
||||
class ScriptDebugger {
|
||||
typedef ScriptLanguage::StackInfo StackInfo;
|
||||
|
||||
bool skip_breakpoints = false;
|
||||
bool ignore_error_breaks = false;
|
||||
|
||||
HashMap<int, HashSet<StringName>> breakpoints;
|
||||
|
||||
static inline thread_local int lines_left = -1;
|
||||
static inline thread_local int depth = -1;
|
||||
static inline thread_local ScriptLanguage *break_lang = nullptr;
|
||||
static thread_local Vector<StackInfo> error_stack_info;
|
||||
|
||||
public:
|
||||
void set_lines_left(int p_left);
|
||||
_ALWAYS_INLINE_ int get_lines_left() const {
|
||||
return lines_left;
|
||||
}
|
||||
|
||||
void set_depth(int p_depth);
|
||||
_ALWAYS_INLINE_ int get_depth() const {
|
||||
return depth;
|
||||
}
|
||||
|
||||
String breakpoint_find_source(const String &p_source) const;
|
||||
void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
|
||||
ScriptLanguage *get_break_language() { return break_lang; }
|
||||
void set_skip_breakpoints(bool p_skip_breakpoints);
|
||||
bool is_skipping_breakpoints();
|
||||
void set_ignore_error_breaks(bool p_ignore);
|
||||
bool is_ignoring_error_breaks();
|
||||
void insert_breakpoint(int p_line, const StringName &p_source);
|
||||
void remove_breakpoint(int p_line, const StringName &p_source);
|
||||
_ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const {
|
||||
if (likely(!breakpoints.has(p_line))) {
|
||||
return false;
|
||||
}
|
||||
return breakpoints[p_line].has(p_source);
|
||||
}
|
||||
void clear_breakpoints();
|
||||
const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }
|
||||
|
||||
void debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
|
||||
ScriptLanguage *get_break_language() const;
|
||||
|
||||
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info);
|
||||
Vector<StackInfo> get_error_stack_info() const;
|
||||
ScriptDebugger() {}
|
||||
};
|
||||
170
core/doc_data.cpp
Normal file
170
core/doc_data.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/**************************************************************************/
|
||||
/* doc_data.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 "doc_data.h"
|
||||
|
||||
String DocData::get_default_value_string(const Variant &p_value) {
|
||||
const Variant::Type type = p_value.get_type();
|
||||
if (type == Variant::ARRAY) {
|
||||
return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' ');
|
||||
} else if (type == Variant::DICTIONARY) {
|
||||
return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' ');
|
||||
} else if (type == Variant::INT) {
|
||||
return itos(p_value);
|
||||
} else if (type == Variant::FLOAT) {
|
||||
// Since some values are 32-bit internally, use 32-bit for all
|
||||
// documentation values to avoid garbage digits at the end.
|
||||
const String s = String::num_scientific((float)p_value);
|
||||
// Use float literals for floats in the documentation for clarity.
|
||||
if (s != "inf" && s != "-inf" && s != "nan") {
|
||||
if (!s.contains_char('.') && !s.contains_char('e')) {
|
||||
return s + ".0";
|
||||
}
|
||||
}
|
||||
return s;
|
||||
} else {
|
||||
return p_value.get_construct_string().replace_char('\n', ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo) {
|
||||
if (p_retinfo.type == Variant::INT && p_retinfo.hint == PROPERTY_HINT_INT_IS_POINTER) {
|
||||
p_method.return_type = p_retinfo.hint_string;
|
||||
if (p_method.return_type.is_empty()) {
|
||||
p_method.return_type = "void*";
|
||||
} else {
|
||||
p_method.return_type += "*";
|
||||
}
|
||||
} else if (p_retinfo.type == Variant::INT && p_retinfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
|
||||
p_method.return_enum = p_retinfo.class_name;
|
||||
if (p_method.return_enum.begins_with("_")) { //proxy class
|
||||
p_method.return_enum = p_method.return_enum.substr(1);
|
||||
}
|
||||
p_method.return_is_bitfield = p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD;
|
||||
p_method.return_type = "int";
|
||||
} else if (p_retinfo.class_name != StringName()) {
|
||||
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) {
|
||||
p_method.return_type = "Variant";
|
||||
} else if (p_retinfo.type == Variant::NIL) {
|
||||
p_method.return_type = "void";
|
||||
} else {
|
||||
p_method.return_type = Variant::get_type_name(p_retinfo.type);
|
||||
}
|
||||
}
|
||||
|
||||
void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo) {
|
||||
p_argument.name = p_arginfo.name;
|
||||
|
||||
if (p_arginfo.type == Variant::INT && p_arginfo.hint == PROPERTY_HINT_INT_IS_POINTER) {
|
||||
p_argument.type = p_arginfo.hint_string;
|
||||
if (p_argument.type.is_empty()) {
|
||||
p_argument.type = "void*";
|
||||
} else {
|
||||
p_argument.type += "*";
|
||||
}
|
||||
} else if (p_arginfo.type == Variant::INT && p_arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
|
||||
p_argument.enumeration = p_arginfo.class_name;
|
||||
if (p_argument.enumeration.begins_with("_")) { //proxy class
|
||||
p_argument.enumeration = p_argument.enumeration.substr(1);
|
||||
}
|
||||
p_argument.is_bitfield = p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD;
|
||||
p_argument.type = "int";
|
||||
} else if (p_arginfo.class_name != StringName()) {
|
||||
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) {
|
||||
// Parameters cannot be void, so PROPERTY_USAGE_NIL_IS_VARIANT is not necessary
|
||||
p_argument.type = "Variant";
|
||||
} else {
|
||||
p_argument.type = Variant::get_type_name(p_arginfo.type);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (p_methodinfo.flags & METHOD_FLAG_VIRTUAL) {
|
||||
p_method.qualifiers = "virtual";
|
||||
}
|
||||
|
||||
if (p_methodinfo.flags & METHOD_FLAG_VIRTUAL_REQUIRED) {
|
||||
if (!p_method.qualifiers.is_empty()) {
|
||||
p_method.qualifiers += " ";
|
||||
}
|
||||
p_method.qualifiers += "required";
|
||||
}
|
||||
|
||||
if (p_methodinfo.flags & METHOD_FLAG_CONST) {
|
||||
if (!p_method.qualifiers.is_empty()) {
|
||||
p_method.qualifiers += " ";
|
||||
}
|
||||
p_method.qualifiers += "const";
|
||||
}
|
||||
|
||||
if (p_methodinfo.flags & METHOD_FLAG_VARARG) {
|
||||
if (!p_method.qualifiers.is_empty()) {
|
||||
p_method.qualifiers += " ";
|
||||
}
|
||||
p_method.qualifiers += "vararg";
|
||||
}
|
||||
|
||||
if (p_methodinfo.flags & METHOD_FLAG_STATIC) {
|
||||
if (!p_method.qualifiers.is_empty()) {
|
||||
p_method.qualifiers += " ";
|
||||
}
|
||||
p_method.qualifiers += "static";
|
||||
}
|
||||
|
||||
return_doc_from_retinfo(p_method, p_methodinfo.return_val);
|
||||
|
||||
for (int64_t i = 0; i < p_methodinfo.arguments.size(); ++i) {
|
||||
DocData::ArgumentDoc argument;
|
||||
argument_doc_from_arginfo(argument, p_methodinfo.arguments[i]);
|
||||
int64_t default_arg_index = i - (p_methodinfo.arguments.size() - p_methodinfo.default_arguments.size());
|
||||
if (default_arg_index >= 0) {
|
||||
Variant default_arg = p_methodinfo.default_arguments[default_arg_index];
|
||||
argument.default_value = get_default_value_string(default_arg);
|
||||
}
|
||||
p_method.arguments.push_back(argument);
|
||||
}
|
||||
}
|
||||
983
core/doc_data.h
Normal file
983
core/doc_data.h
Normal file
@@ -0,0 +1,983 @@
|
||||
/**************************************************************************/
|
||||
/* doc_data.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/xml_parser.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class DocData {
|
||||
public:
|
||||
struct ArgumentDoc {
|
||||
String name;
|
||||
String type;
|
||||
String enumeration;
|
||||
bool is_bitfield = false;
|
||||
String default_value;
|
||||
bool operator<(const ArgumentDoc &p_arg) const {
|
||||
if (name == p_arg.name) {
|
||||
return type < p_arg.type;
|
||||
}
|
||||
return name < p_arg.name;
|
||||
}
|
||||
static ArgumentDoc from_dict(const Dictionary &p_dict) {
|
||||
ArgumentDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
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")) {
|
||||
doc.is_bitfield = p_dict["is_bitfield"];
|
||||
}
|
||||
}
|
||||
|
||||
if (p_dict.has("default_value")) {
|
||||
doc.default_value = p_dict["default_value"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const ArgumentDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.type.is_empty()) {
|
||||
dict["type"] = p_doc.type;
|
||||
}
|
||||
|
||||
if (!p_doc.enumeration.is_empty()) {
|
||||
dict["enumeration"] = p_doc.enumeration;
|
||||
dict["is_bitfield"] = p_doc.is_bitfield;
|
||||
}
|
||||
|
||||
if (!p_doc.default_value.is_empty()) {
|
||||
dict["default_value"] = p_doc.default_value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct MethodDoc {
|
||||
String name;
|
||||
String return_type;
|
||||
String return_enum;
|
||||
bool return_is_bitfield = false;
|
||||
String qualifiers;
|
||||
String description;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
Vector<ArgumentDoc> arguments;
|
||||
// NOTE: Only for GDScript for now. The rest argument is not saved to the XML file.
|
||||
ArgumentDoc rest_argument;
|
||||
Vector<int> errors_returned;
|
||||
String keywords;
|
||||
bool operator<(const MethodDoc &p_method) const {
|
||||
if (name == p_method.name) {
|
||||
// Must be an operator or a constructor since there is no other overloading
|
||||
if (name.left(8) == "operator") {
|
||||
if (arguments.size() == p_method.arguments.size()) {
|
||||
if (arguments.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
return arguments[0].type < p_method.arguments[0].type;
|
||||
}
|
||||
return arguments.size() < p_method.arguments.size();
|
||||
} else {
|
||||
// Must be a constructor
|
||||
// We want this arbitrary order for a class "Foo":
|
||||
// - 1. Default constructor: Foo()
|
||||
// - 2. Copy constructor: Foo(Foo)
|
||||
// - 3+. Other constructors Foo(Bar, ...) based on first argument's name
|
||||
if (arguments.is_empty() || p_method.arguments.is_empty()) { // 1.
|
||||
return arguments.size() < p_method.arguments.size();
|
||||
}
|
||||
if (arguments[0].type == return_type || p_method.arguments[0].type == p_method.return_type) { // 2.
|
||||
return (arguments[0].type == return_type) || (p_method.arguments[0].type != p_method.return_type);
|
||||
}
|
||||
return arguments[0] < p_method.arguments[0];
|
||||
}
|
||||
}
|
||||
return name.naturalcasecmp_to(p_method.name) < 0;
|
||||
}
|
||||
static MethodDoc from_dict(const Dictionary &p_dict) {
|
||||
MethodDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
if (p_dict.has("return_type")) {
|
||||
doc.return_type = p_dict["return_type"];
|
||||
}
|
||||
|
||||
if (p_dict.has("return_enum")) {
|
||||
doc.return_enum = p_dict["return_enum"];
|
||||
if (p_dict.has("return_is_bitfield")) {
|
||||
doc.return_is_bitfield = p_dict["return_is_bitfield"];
|
||||
}
|
||||
}
|
||||
|
||||
if (p_dict.has("qualifiers")) {
|
||||
doc.qualifiers = p_dict["qualifiers"];
|
||||
}
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
doc.description = p_dict["description"];
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (p_dict.has("is_deprecated")) {
|
||||
doc.is_deprecated = p_dict["is_deprecated"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_experimental")) {
|
||||
doc.is_experimental = p_dict["is_experimental"];
|
||||
}
|
||||
#endif
|
||||
|
||||
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"];
|
||||
}
|
||||
|
||||
Array arguments;
|
||||
if (p_dict.has("arguments")) {
|
||||
arguments = p_dict["arguments"];
|
||||
}
|
||||
for (int i = 0; i < arguments.size(); i++) {
|
||||
doc.arguments.push_back(ArgumentDoc::from_dict(arguments[i]));
|
||||
}
|
||||
|
||||
Array errors_returned;
|
||||
if (p_dict.has("errors_returned")) {
|
||||
errors_returned = p_dict["errors_returned"];
|
||||
}
|
||||
for (int i = 0; i < errors_returned.size(); i++) {
|
||||
doc.errors_returned.push_back(errors_returned[i]);
|
||||
}
|
||||
|
||||
if (p_dict.has("keywords")) {
|
||||
doc.keywords = p_dict["keywords"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const MethodDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.return_type.is_empty()) {
|
||||
dict["return_type"] = p_doc.return_type;
|
||||
}
|
||||
|
||||
if (!p_doc.return_enum.is_empty()) {
|
||||
dict["return_enum"] = p_doc.return_enum;
|
||||
dict["return_is_bitfield"] = p_doc.return_is_bitfield;
|
||||
}
|
||||
|
||||
if (!p_doc.qualifiers.is_empty()) {
|
||||
dict["qualifiers"] = p_doc.qualifiers;
|
||||
}
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
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.keywords.is_empty()) {
|
||||
dict["keywords"] = p_doc.keywords;
|
||||
}
|
||||
|
||||
if (!p_doc.arguments.is_empty()) {
|
||||
Array arguments;
|
||||
for (int i = 0; i < p_doc.arguments.size(); i++) {
|
||||
arguments.push_back(ArgumentDoc::to_dict(p_doc.arguments[i]));
|
||||
}
|
||||
dict["arguments"] = arguments;
|
||||
}
|
||||
|
||||
if (!p_doc.errors_returned.is_empty()) {
|
||||
Array errors_returned;
|
||||
for (int i = 0; i < p_doc.errors_returned.size(); i++) {
|
||||
errors_returned.push_back(p_doc.errors_returned[i]);
|
||||
}
|
||||
dict["errors_returned"] = errors_returned;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct ConstantDoc {
|
||||
String name;
|
||||
String value;
|
||||
bool is_value_valid = false;
|
||||
String type;
|
||||
String enumeration;
|
||||
bool is_bitfield = false;
|
||||
String description;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
String keywords;
|
||||
bool operator<(const ConstantDoc &p_const) const {
|
||||
return name < p_const.name;
|
||||
}
|
||||
static ConstantDoc from_dict(const Dictionary &p_dict) {
|
||||
ConstantDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
if (p_dict.has("value")) {
|
||||
doc.value = p_dict["value"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_value_valid")) {
|
||||
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")) {
|
||||
doc.is_bitfield = p_dict["is_bitfield"];
|
||||
}
|
||||
}
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
doc.description = p_dict["description"];
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (p_dict.has("is_deprecated")) {
|
||||
doc.is_deprecated = p_dict["is_deprecated"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_experimental")) {
|
||||
doc.is_experimental = p_dict["is_experimental"];
|
||||
}
|
||||
#endif
|
||||
|
||||
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("keywords")) {
|
||||
doc.keywords = p_dict["keywords"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const ConstantDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.value.is_empty()) {
|
||||
dict["value"] = p_doc.value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
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.keywords.is_empty()) {
|
||||
dict["keywords"] = p_doc.keywords;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct PropertyDoc {
|
||||
String name;
|
||||
String type;
|
||||
String enumeration;
|
||||
bool is_bitfield = false;
|
||||
String description;
|
||||
String setter, getter;
|
||||
String default_value;
|
||||
bool overridden = false;
|
||||
String overrides;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
String keywords;
|
||||
bool operator<(const PropertyDoc &p_prop) const {
|
||||
return name.naturalcasecmp_to(p_prop.name) < 0;
|
||||
}
|
||||
static PropertyDoc from_dict(const Dictionary &p_dict) {
|
||||
PropertyDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
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")) {
|
||||
doc.is_bitfield = p_dict["is_bitfield"];
|
||||
}
|
||||
}
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
doc.description = p_dict["description"];
|
||||
}
|
||||
|
||||
if (p_dict.has("setter")) {
|
||||
doc.setter = p_dict["setter"];
|
||||
}
|
||||
|
||||
if (p_dict.has("getter")) {
|
||||
doc.getter = p_dict["getter"];
|
||||
}
|
||||
|
||||
if (p_dict.has("default_value")) {
|
||||
doc.default_value = p_dict["default_value"];
|
||||
}
|
||||
|
||||
if (p_dict.has("overridden")) {
|
||||
doc.overridden = p_dict["overridden"];
|
||||
}
|
||||
|
||||
if (p_dict.has("overrides")) {
|
||||
doc.overrides = p_dict["overrides"];
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (p_dict.has("is_deprecated")) {
|
||||
doc.is_deprecated = p_dict["is_deprecated"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_experimental")) {
|
||||
doc.is_experimental = p_dict["is_experimental"];
|
||||
}
|
||||
#endif
|
||||
|
||||
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("keywords")) {
|
||||
doc.keywords = p_dict["keywords"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const PropertyDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.type.is_empty()) {
|
||||
dict["type"] = p_doc.type;
|
||||
}
|
||||
|
||||
if (!p_doc.enumeration.is_empty()) {
|
||||
dict["enumeration"] = p_doc.enumeration;
|
||||
dict["is_bitfield"] = p_doc.is_bitfield;
|
||||
}
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
dict["description"] = p_doc.description;
|
||||
}
|
||||
|
||||
if (!p_doc.setter.is_empty()) {
|
||||
dict["setter"] = p_doc.setter;
|
||||
}
|
||||
|
||||
if (!p_doc.getter.is_empty()) {
|
||||
dict["getter"] = p_doc.getter;
|
||||
}
|
||||
|
||||
if (!p_doc.default_value.is_empty()) {
|
||||
dict["default_value"] = p_doc.default_value;
|
||||
}
|
||||
|
||||
dict["overridden"] = p_doc.overridden;
|
||||
|
||||
if (!p_doc.overrides.is_empty()) {
|
||||
dict["overrides"] = p_doc.overrides;
|
||||
}
|
||||
|
||||
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.keywords.is_empty()) {
|
||||
dict["keywords"] = p_doc.keywords;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct ThemeItemDoc {
|
||||
String name;
|
||||
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 {
|
||||
// First sort by the data type, then by name.
|
||||
if (data_type == p_theme_item.data_type) {
|
||||
return name.naturalcasecmp_to(p_theme_item.name) < 0;
|
||||
}
|
||||
return data_type < p_theme_item.data_type;
|
||||
}
|
||||
static ThemeItemDoc from_dict(const Dictionary &p_dict) {
|
||||
ThemeItemDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
if (p_dict.has("type")) {
|
||||
doc.type = p_dict["type"];
|
||||
}
|
||||
|
||||
if (p_dict.has("data_type")) {
|
||||
doc.data_type = p_dict["data_type"];
|
||||
}
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
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"];
|
||||
}
|
||||
|
||||
if (p_dict.has("keywords")) {
|
||||
doc.keywords = p_dict["keywords"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const ThemeItemDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.type.is_empty()) {
|
||||
dict["type"] = p_doc.type;
|
||||
}
|
||||
|
||||
if (!p_doc.data_type.is_empty()) {
|
||||
dict["data_type"] = p_doc.data_type;
|
||||
}
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!p_doc.keywords.is_empty()) {
|
||||
dict["keywords"] = p_doc.keywords;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct TutorialDoc {
|
||||
String link;
|
||||
String title;
|
||||
static TutorialDoc from_dict(const Dictionary &p_dict) {
|
||||
TutorialDoc doc;
|
||||
|
||||
if (p_dict.has("link")) {
|
||||
doc.link = p_dict["link"];
|
||||
}
|
||||
|
||||
if (p_dict.has("title")) {
|
||||
doc.title = p_dict["title"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const TutorialDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.link.is_empty()) {
|
||||
dict["link"] = p_doc.link;
|
||||
}
|
||||
|
||||
if (!p_doc.title.is_empty()) {
|
||||
dict["title"] = p_doc.title;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct EnumDoc {
|
||||
String description;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
static EnumDoc from_dict(const Dictionary &p_dict) {
|
||||
EnumDoc doc;
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
doc.description = p_dict["description"];
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (p_dict.has("is_deprecated")) {
|
||||
doc.is_deprecated = p_dict["is_deprecated"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_experimental")) {
|
||||
doc.is_experimental = p_dict["is_experimental"];
|
||||
}
|
||||
#endif
|
||||
|
||||
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"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const EnumDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
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;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClassDoc {
|
||||
String name;
|
||||
String inherits;
|
||||
String brief_description;
|
||||
String description;
|
||||
String keywords;
|
||||
Vector<TutorialDoc> tutorials;
|
||||
Vector<MethodDoc> constructors;
|
||||
Vector<MethodDoc> methods;
|
||||
Vector<MethodDoc> operators;
|
||||
Vector<MethodDoc> signals;
|
||||
Vector<ConstantDoc> constants;
|
||||
HashMap<String, EnumDoc> enums;
|
||||
Vector<PropertyDoc> properties;
|
||||
Vector<MethodDoc> annotations;
|
||||
Vector<ThemeItemDoc> theme_properties;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
bool is_script_doc = false;
|
||||
String script_path;
|
||||
bool operator<(const ClassDoc &p_class) const {
|
||||
return name < p_class.name;
|
||||
}
|
||||
static ClassDoc from_dict(const Dictionary &p_dict) {
|
||||
ClassDoc doc;
|
||||
|
||||
if (p_dict.has("name")) {
|
||||
doc.name = p_dict["name"];
|
||||
}
|
||||
|
||||
if (p_dict.has("inherits")) {
|
||||
doc.inherits = p_dict["inherits"];
|
||||
}
|
||||
|
||||
if (p_dict.has("brief_description")) {
|
||||
doc.brief_description = p_dict["brief_description"];
|
||||
}
|
||||
|
||||
if (p_dict.has("description")) {
|
||||
doc.description = p_dict["description"];
|
||||
}
|
||||
|
||||
if (p_dict.has("keywords")) {
|
||||
doc.keywords = p_dict["keywords"];
|
||||
}
|
||||
|
||||
Array tutorials;
|
||||
if (p_dict.has("tutorials")) {
|
||||
tutorials = p_dict["tutorials"];
|
||||
}
|
||||
for (int i = 0; i < tutorials.size(); i++) {
|
||||
doc.tutorials.push_back(TutorialDoc::from_dict(tutorials[i]));
|
||||
}
|
||||
|
||||
Array constructors;
|
||||
if (p_dict.has("constructors")) {
|
||||
constructors = p_dict["constructors"];
|
||||
}
|
||||
for (int i = 0; i < constructors.size(); i++) {
|
||||
doc.constructors.push_back(MethodDoc::from_dict(constructors[i]));
|
||||
}
|
||||
|
||||
Array methods;
|
||||
if (p_dict.has("methods")) {
|
||||
methods = p_dict["methods"];
|
||||
}
|
||||
for (int i = 0; i < methods.size(); i++) {
|
||||
doc.methods.push_back(MethodDoc::from_dict(methods[i]));
|
||||
}
|
||||
|
||||
Array operators;
|
||||
if (p_dict.has("operators")) {
|
||||
operators = p_dict["operators"];
|
||||
}
|
||||
for (int i = 0; i < operators.size(); i++) {
|
||||
doc.operators.push_back(MethodDoc::from_dict(operators[i]));
|
||||
}
|
||||
|
||||
Array signals;
|
||||
if (p_dict.has("signals")) {
|
||||
signals = p_dict["signals"];
|
||||
}
|
||||
for (int i = 0; i < signals.size(); i++) {
|
||||
doc.signals.push_back(MethodDoc::from_dict(signals[i]));
|
||||
}
|
||||
|
||||
Array constants;
|
||||
if (p_dict.has("constants")) {
|
||||
constants = p_dict["constants"];
|
||||
}
|
||||
for (int i = 0; i < constants.size(); i++) {
|
||||
doc.constants.push_back(ConstantDoc::from_dict(constants[i]));
|
||||
}
|
||||
|
||||
Dictionary enums;
|
||||
if (p_dict.has("enums")) {
|
||||
enums = p_dict["enums"];
|
||||
}
|
||||
for (const KeyValue<Variant, Variant> &kv : enums) {
|
||||
doc.enums[kv.key] = EnumDoc::from_dict(kv.value);
|
||||
}
|
||||
|
||||
Array properties;
|
||||
if (p_dict.has("properties")) {
|
||||
properties = p_dict["properties"];
|
||||
}
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
doc.properties.push_back(PropertyDoc::from_dict(properties[i]));
|
||||
}
|
||||
|
||||
Array annotations;
|
||||
if (p_dict.has("annotations")) {
|
||||
annotations = p_dict["annotations"];
|
||||
}
|
||||
for (int i = 0; i < annotations.size(); i++) {
|
||||
doc.annotations.push_back(MethodDoc::from_dict(annotations[i]));
|
||||
}
|
||||
|
||||
Array theme_properties;
|
||||
if (p_dict.has("theme_properties")) {
|
||||
theme_properties = p_dict["theme_properties"];
|
||||
}
|
||||
for (int i = 0; i < theme_properties.size(); i++) {
|
||||
doc.theme_properties.push_back(ThemeItemDoc::from_dict(theme_properties[i]));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (p_dict.has("is_deprecated")) {
|
||||
doc.is_deprecated = p_dict["is_deprecated"];
|
||||
}
|
||||
|
||||
if (p_dict.has("is_experimental")) {
|
||||
doc.is_experimental = p_dict["is_experimental"];
|
||||
}
|
||||
#endif
|
||||
|
||||
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("is_script_doc")) {
|
||||
doc.is_script_doc = p_dict["is_script_doc"];
|
||||
}
|
||||
|
||||
if (p_dict.has("script_path")) {
|
||||
doc.script_path = p_dict["script_path"];
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
static Dictionary to_dict(const ClassDoc &p_doc) {
|
||||
Dictionary dict;
|
||||
|
||||
if (!p_doc.name.is_empty()) {
|
||||
dict["name"] = p_doc.name;
|
||||
}
|
||||
|
||||
if (!p_doc.inherits.is_empty()) {
|
||||
dict["inherits"] = p_doc.inherits;
|
||||
}
|
||||
|
||||
if (!p_doc.brief_description.is_empty()) {
|
||||
dict["brief_description"] = p_doc.brief_description;
|
||||
}
|
||||
|
||||
if (!p_doc.description.is_empty()) {
|
||||
dict["description"] = p_doc.description;
|
||||
}
|
||||
|
||||
if (!p_doc.tutorials.is_empty()) {
|
||||
Array tutorials;
|
||||
for (int i = 0; i < p_doc.tutorials.size(); i++) {
|
||||
tutorials.push_back(TutorialDoc::to_dict(p_doc.tutorials[i]));
|
||||
}
|
||||
dict["tutorials"] = tutorials;
|
||||
}
|
||||
|
||||
if (!p_doc.constructors.is_empty()) {
|
||||
Array constructors;
|
||||
for (int i = 0; i < p_doc.constructors.size(); i++) {
|
||||
constructors.push_back(MethodDoc::to_dict(p_doc.constructors[i]));
|
||||
}
|
||||
dict["constructors"] = constructors;
|
||||
}
|
||||
|
||||
if (!p_doc.methods.is_empty()) {
|
||||
Array methods;
|
||||
for (int i = 0; i < p_doc.methods.size(); i++) {
|
||||
methods.push_back(MethodDoc::to_dict(p_doc.methods[i]));
|
||||
}
|
||||
dict["methods"] = methods;
|
||||
}
|
||||
|
||||
if (!p_doc.operators.is_empty()) {
|
||||
Array operators;
|
||||
for (int i = 0; i < p_doc.operators.size(); i++) {
|
||||
operators.push_back(MethodDoc::to_dict(p_doc.operators[i]));
|
||||
}
|
||||
dict["operators"] = operators;
|
||||
}
|
||||
|
||||
if (!p_doc.signals.is_empty()) {
|
||||
Array signals;
|
||||
for (int i = 0; i < p_doc.signals.size(); i++) {
|
||||
signals.push_back(MethodDoc::to_dict(p_doc.signals[i]));
|
||||
}
|
||||
dict["signals"] = signals;
|
||||
}
|
||||
|
||||
if (!p_doc.constants.is_empty()) {
|
||||
Array constants;
|
||||
for (int i = 0; i < p_doc.constants.size(); i++) {
|
||||
constants.push_back(ConstantDoc::to_dict(p_doc.constants[i]));
|
||||
}
|
||||
dict["constants"] = constants;
|
||||
}
|
||||
|
||||
if (!p_doc.enums.is_empty()) {
|
||||
Dictionary enums;
|
||||
for (const KeyValue<String, EnumDoc> &E : p_doc.enums) {
|
||||
enums[E.key] = EnumDoc::to_dict(E.value);
|
||||
}
|
||||
dict["enums"] = enums;
|
||||
}
|
||||
|
||||
if (!p_doc.properties.is_empty()) {
|
||||
Array properties;
|
||||
for (int i = 0; i < p_doc.properties.size(); i++) {
|
||||
properties.push_back(PropertyDoc::to_dict(p_doc.properties[i]));
|
||||
}
|
||||
dict["properties"] = properties;
|
||||
}
|
||||
|
||||
if (!p_doc.annotations.is_empty()) {
|
||||
Array annotations;
|
||||
for (int i = 0; i < p_doc.annotations.size(); i++) {
|
||||
annotations.push_back(MethodDoc::to_dict(p_doc.annotations[i]));
|
||||
}
|
||||
dict["annotations"] = annotations;
|
||||
}
|
||||
|
||||
if (!p_doc.theme_properties.is_empty()) {
|
||||
Array theme_properties;
|
||||
for (int i = 0; i < p_doc.theme_properties.size(); i++) {
|
||||
theme_properties.push_back(ThemeItemDoc::to_dict(p_doc.theme_properties[i]));
|
||||
}
|
||||
dict["theme_properties"] = theme_properties;
|
||||
}
|
||||
|
||||
if (p_doc.is_deprecated) {
|
||||
dict["deprecated"] = p_doc.deprecated_message;
|
||||
}
|
||||
|
||||
if (p_doc.is_experimental) {
|
||||
dict["experimental"] = p_doc.experimental_message;
|
||||
}
|
||||
|
||||
dict["is_script_doc"] = p_doc.is_script_doc;
|
||||
|
||||
if (!p_doc.script_path.is_empty()) {
|
||||
dict["script_path"] = p_doc.script_path;
|
||||
}
|
||||
|
||||
if (!p_doc.keywords.is_empty()) {
|
||||
dict["keywords"] = p_doc.keywords;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
static String get_default_value_string(const Variant &p_value);
|
||||
|
||||
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 method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc);
|
||||
};
|
||||
8
core/error/SCsub
Normal file
8
core/error/SCsub
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env_error = env.Clone()
|
||||
|
||||
env_error.add_source_files(env.core_sources, "*.cpp")
|
||||
87
core/error/error_list.cpp
Normal file
87
core/error/error_list.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/**************************************************************************/
|
||||
/* error_list.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 "error_list.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
const char *error_names[] = {
|
||||
"OK", // OK
|
||||
"Failed", // FAILED
|
||||
"Unavailable", // ERR_UNAVAILABLE
|
||||
"Unconfigured", // ERR_UNCONFIGURED
|
||||
"Unauthorized", // ERR_UNAUTHORIZED
|
||||
"Parameter out of range", // ERR_PARAMETER_RANGE_ERROR
|
||||
"Out of memory", // ERR_OUT_OF_MEMORY
|
||||
"File not found", // ERR_FILE_NOT_FOUND
|
||||
"File: Bad drive", // ERR_FILE_BAD_DRIVE
|
||||
"File: Bad path", // ERR_FILE_BAD_PATH
|
||||
"File: Permission denied", // ERR_FILE_NO_PERMISSION
|
||||
"File already in use", // ERR_FILE_ALREADY_IN_USE
|
||||
"Can't open file", // ERR_FILE_CANT_OPEN
|
||||
"Can't write file", // ERR_FILE_CANT_WRITE
|
||||
"Can't read file", // ERR_FILE_CANT_READ
|
||||
"File unrecognized", // ERR_FILE_UNRECOGNIZED
|
||||
"File corrupt", // ERR_FILE_CORRUPT
|
||||
"Missing dependencies for file", // ERR_FILE_MISSING_DEPENDENCIES
|
||||
"End of file", // ERR_FILE_EOF
|
||||
"Can't open", // ERR_CANT_OPEN
|
||||
"Can't create", // ERR_CANT_CREATE
|
||||
"Query failed", // ERR_QUERY_FAILED
|
||||
"Already in use", // ERR_ALREADY_IN_USE
|
||||
"Locked", // ERR_LOCKED
|
||||
"Timeout", // ERR_TIMEOUT
|
||||
"Can't connect", // ERR_CANT_CONNECT
|
||||
"Can't resolve", // ERR_CANT_RESOLVE
|
||||
"Connection error", // ERR_CONNECTION_ERROR
|
||||
"Can't acquire resource", // ERR_CANT_ACQUIRE_RESOURCE
|
||||
"Can't fork", // ERR_CANT_FORK
|
||||
"Invalid data", // ERR_INVALID_DATA
|
||||
"Invalid parameter", // ERR_INVALID_PARAMETER
|
||||
"Already exists", // ERR_ALREADY_EXISTS
|
||||
"Does not exist", // ERR_DOES_NOT_EXIST
|
||||
"Can't read database", // ERR_DATABASE_CANT_READ
|
||||
"Can't write database", // ERR_DATABASE_CANT_WRITE
|
||||
"Compilation failed", // ERR_COMPILATION_FAILED
|
||||
"Method not found", // ERR_METHOD_NOT_FOUND
|
||||
"Link failed", // ERR_LINK_FAILED
|
||||
"Script failed", // ERR_SCRIPT_FAILED
|
||||
"Cyclic link detected", // ERR_CYCLIC_LINK
|
||||
"Invalid declaration", // ERR_INVALID_DECLARATION
|
||||
"Duplicate symbol", // ERR_DUPLICATE_SYMBOL
|
||||
"Parse error", // ERR_PARSE_ERROR
|
||||
"Busy", // ERR_BUSY
|
||||
"Skip", // ERR_SKIP
|
||||
"Help", // ERR_HELP
|
||||
"Bug", // ERR_BUG
|
||||
"Printer on fire", // ERR_PRINTER_ON_FIRE
|
||||
};
|
||||
|
||||
static_assert(std::size(error_names) == ERR_MAX);
|
||||
99
core/error/error_list.h
Normal file
99
core/error/error_list.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/**************************************************************************/
|
||||
/* error_list.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** Error List. Please never compare an error against FAILED
|
||||
* Either do result != OK , or !result. This way, Error fail
|
||||
* values can be more detailed in the future.
|
||||
*
|
||||
* This is a generic error list, mainly for organizing a language of returning errors.
|
||||
*
|
||||
* Errors:
|
||||
* - 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 {
|
||||
OK, // (0)
|
||||
FAILED, ///< Generic fail error
|
||||
ERR_UNAVAILABLE, ///< What is requested is unsupported/unavailable
|
||||
ERR_UNCONFIGURED, ///< The object being used hasn't been properly set up yet
|
||||
ERR_UNAUTHORIZED, ///< Missing credentials for requested resource
|
||||
ERR_PARAMETER_RANGE_ERROR, ///< Parameter given out of range (5)
|
||||
ERR_OUT_OF_MEMORY, ///< Out of memory
|
||||
ERR_FILE_NOT_FOUND,
|
||||
ERR_FILE_BAD_DRIVE,
|
||||
ERR_FILE_BAD_PATH,
|
||||
ERR_FILE_NO_PERMISSION, // (10)
|
||||
ERR_FILE_ALREADY_IN_USE,
|
||||
ERR_FILE_CANT_OPEN,
|
||||
ERR_FILE_CANT_WRITE,
|
||||
ERR_FILE_CANT_READ,
|
||||
ERR_FILE_UNRECOGNIZED, // (15)
|
||||
ERR_FILE_CORRUPT,
|
||||
ERR_FILE_MISSING_DEPENDENCIES,
|
||||
ERR_FILE_EOF,
|
||||
ERR_CANT_OPEN, ///< Can't open a resource/socket/file
|
||||
ERR_CANT_CREATE, // (20)
|
||||
ERR_QUERY_FAILED,
|
||||
ERR_ALREADY_IN_USE,
|
||||
ERR_LOCKED, ///< resource is locked
|
||||
ERR_TIMEOUT,
|
||||
ERR_CANT_CONNECT, // (25)
|
||||
ERR_CANT_RESOLVE,
|
||||
ERR_CONNECTION_ERROR,
|
||||
ERR_CANT_ACQUIRE_RESOURCE,
|
||||
ERR_CANT_FORK,
|
||||
ERR_INVALID_DATA, ///< Data passed is invalid (30)
|
||||
ERR_INVALID_PARAMETER, ///< Parameter passed is invalid
|
||||
ERR_ALREADY_EXISTS, ///< When adding, item already exists
|
||||
ERR_DOES_NOT_EXIST, ///< When retrieving/erasing, if item does not exist
|
||||
ERR_DATABASE_CANT_READ, ///< database is full
|
||||
ERR_DATABASE_CANT_WRITE, ///< database is full (35)
|
||||
ERR_COMPILATION_FAILED,
|
||||
ERR_METHOD_NOT_FOUND,
|
||||
ERR_LINK_FAILED,
|
||||
ERR_SCRIPT_FAILED,
|
||||
ERR_CYCLIC_LINK, // (40)
|
||||
ERR_INVALID_DECLARATION,
|
||||
ERR_DUPLICATE_SYMBOL,
|
||||
ERR_PARSE_ERROR,
|
||||
ERR_BUSY,
|
||||
ERR_SKIP, // (45)
|
||||
ERR_HELP, ///< user requested help!!
|
||||
ERR_BUG, ///< a bug in the software certainly happened, due to a double check failing or unexpected behavior.
|
||||
ERR_PRINTER_ON_FIRE, /// the parallel port printer is engulfed in flames
|
||||
ERR_MAX, // Not being returned, value represents the number of errors
|
||||
};
|
||||
|
||||
extern const char *error_names[];
|
||||
242
core/error/error_macros.cpp
Normal file
242
core/error/error_macros.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/**************************************************************************/
|
||||
/* error_macros.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 "error_macros.h"
|
||||
|
||||
#include "core/io/logger.h"
|
||||
#include "core/object/object_id.h"
|
||||
#include "core/object/script_language.h"
|
||||
#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;
|
||||
static thread_local bool is_printing_error = false;
|
||||
|
||||
static void _err_print_fallback(const char *p_function, const char *p_file, int p_line, const char *p_error_details, ErrorHandlerType p_type, bool p_reentrance) {
|
||||
if (p_reentrance) {
|
||||
fprintf(stderr, "While attempting to print an error, another error was printed:\n");
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: %s\n", _error_handler_type_string(p_type), p_error_details);
|
||||
|
||||
if (p_function && p_file) {
|
||||
fprintf(stderr, " at: %s (%s:%i)\n", p_function, p_file, p_line);
|
||||
}
|
||||
}
|
||||
|
||||
void add_error_handler(ErrorHandlerList *p_handler) {
|
||||
// If p_handler is already in error_handler_list
|
||||
// we'd better remove it first then we can add it.
|
||||
// This prevent cyclic redundancy.
|
||||
remove_error_handler(p_handler);
|
||||
|
||||
_global_lock();
|
||||
|
||||
p_handler->next = error_handler_list;
|
||||
error_handler_list = p_handler;
|
||||
|
||||
_global_unlock();
|
||||
}
|
||||
|
||||
void remove_error_handler(const ErrorHandlerList *p_handler) {
|
||||
_global_lock();
|
||||
|
||||
ErrorHandlerList *prev = nullptr;
|
||||
ErrorHandlerList *l = error_handler_list;
|
||||
|
||||
while (l) {
|
||||
if (l == p_handler) {
|
||||
if (prev) {
|
||||
prev->next = l->next;
|
||||
} else {
|
||||
error_handler_list = l->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = l;
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
}
|
||||
|
||||
// Errors without messages.
|
||||
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
_err_print_error(p_function, p_file, p_line, p_error, "", p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
_err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), "", p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
// Main error printing function.
|
||||
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
if (is_printing_error) {
|
||||
// Fallback if we're already printing an error, to prevent infinite recursion.
|
||||
const char *err_details = (p_message && *p_message) ? p_message : p_error;
|
||||
_err_print_fallback(p_function, p_file, p_line, err_details, p_type, true);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing_error = true;
|
||||
|
||||
if (OS::get_singleton()) {
|
||||
OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type, ScriptServer::capture_script_backtraces(false));
|
||||
} else {
|
||||
// Fallback if errors happen before OS init or after it's destroyed.
|
||||
const char *err_details = (p_message && *p_message) ? p_message : p_error;
|
||||
_err_print_fallback(p_function, p_file, p_line, err_details, p_type, false);
|
||||
}
|
||||
|
||||
_global_lock();
|
||||
|
||||
ErrorHandlerList *l = error_handler_list;
|
||||
while (l) {
|
||||
l->errfunc(l->userdata, p_function, p_file, p_line, p_error, p_message, p_editor_notify, p_type);
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
|
||||
is_printing_error = false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const char *err_details = p_error.utf8().get_data();
|
||||
|
||||
if (is_printing_error) {
|
||||
// Fallback if we're already printing an error, to prevent infinite recursion.
|
||||
_err_print_fallback(nullptr, nullptr, 0, err_details, p_type, true);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing_error = true;
|
||||
|
||||
if (OS::get_singleton()) {
|
||||
OS::get_singleton()->printerr("%s: %s\n", _error_handler_type_string(p_type), err_details);
|
||||
} else {
|
||||
// Fallback if errors happen before OS init or after it's destroyed.
|
||||
_err_print_fallback(nullptr, nullptr, 0, err_details, p_type, false);
|
||||
}
|
||||
|
||||
_global_lock();
|
||||
|
||||
ErrorHandlerList *l = error_handler_list;
|
||||
while (l) {
|
||||
l->errfunc(l->userdata, "", "", 0, err_details, "", false, p_type);
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
|
||||
is_printing_error = false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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, ErrorHandlerType p_type) {
|
||||
_err_print_error(p_function, p_file, p_line, p_error, p_message.utf8().get_data(), p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
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, ErrorHandlerType p_type) {
|
||||
_err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message.utf8().get_data(), p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
// Index errors. (All combinations of p_message as String or char*.)
|
||||
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, bool p_fatal) {
|
||||
String fstr(p_fatal ? "FATAL: " : "");
|
||||
String err(fstr + "Index " + p_index_str + " = " + itos(p_index) + " is out of bounds (" + p_size_str + " = " + itos(p_size) + ").");
|
||||
_err_print_error(p_function, p_file, p_line, err.utf8().get_data(), p_message, p_editor_notify, 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 String &p_message, bool p_editor_notify, bool p_fatal) {
|
||||
_err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), p_editor_notify, p_fatal);
|
||||
}
|
||||
|
||||
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 = ObjectDB::get_instance<Node>(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
|
||||
}
|
||||
866
core/error/error_macros.h
Normal file
866
core/error/error_macros.h
Normal file
@@ -0,0 +1,866 @@
|
||||
/**************************************************************************/
|
||||
/* error_macros.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/typedefs.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h> // `__fastfail()`.
|
||||
#endif
|
||||
|
||||
class String;
|
||||
class ObjectID;
|
||||
|
||||
enum ErrorHandlerType {
|
||||
ERR_HANDLER_ERROR,
|
||||
ERR_HANDLER_WARNING,
|
||||
ERR_HANDLER_SCRIPT,
|
||||
ERR_HANDLER_SHADER,
|
||||
};
|
||||
|
||||
constexpr const char *_error_handler_type_string(ErrorHandlerType p_type) {
|
||||
switch (p_type) {
|
||||
case ERR_HANDLER_ERROR:
|
||||
return "ERROR";
|
||||
case ERR_HANDLER_WARNING:
|
||||
return "WARNING";
|
||||
case ERR_HANDLER_SCRIPT:
|
||||
return "SCRIPT ERROR";
|
||||
case ERR_HANDLER_SHADER:
|
||||
return "SHADER ERROR";
|
||||
}
|
||||
return "UNKNOWN ERROR";
|
||||
}
|
||||
|
||||
// Pointer to the error handler printing function. Reassign to any function to have errors printed.
|
||||
// Parameters: userdata, function, file, line, error, explanation, type.
|
||||
typedef void (*ErrorHandlerFunc)(void *, const char *, const char *, int p_line, const char *, const char *, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
|
||||
struct ErrorHandlerList {
|
||||
ErrorHandlerFunc errfunc = nullptr;
|
||||
void *userdata = nullptr;
|
||||
|
||||
ErrorHandlerList *next = nullptr;
|
||||
|
||||
ErrorHandlerList() {}
|
||||
};
|
||||
|
||||
void add_error_handler(ErrorHandlerList *p_handler);
|
||||
void remove_error_handler(const ErrorHandlerList *p_handler);
|
||||
|
||||
// Functions used by the error macros.
|
||||
_NO_INLINE_ void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
|
||||
_NO_INLINE_ void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
|
||||
_NO_INLINE_ void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
|
||||
_NO_INLINE_ 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);
|
||||
_NO_INLINE_ 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);
|
||||
_NO_INLINE_ 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);
|
||||
_NO_INLINE_ 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);
|
||||
_NO_INLINE_ 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);
|
||||
_NO_INLINE_ 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__
|
||||
#else
|
||||
#define FUNCTION_STR __FUNCTION__
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/**
|
||||
* Don't use GENERATE_TRAP() directly, should only be used be the macros below.
|
||||
*/
|
||||
#define GENERATE_TRAP() __fastfail(7 /* FAST_FAIL_FATAL_APP_EXIT */)
|
||||
#else
|
||||
/**
|
||||
* Don't use GENERATE_TRAP() directly, should only be used be the macros below.
|
||||
*/
|
||||
#define GENERATE_TRAP() __builtin_trap()
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Error macros.
|
||||
* WARNING: These macros work in the opposite way to assert().
|
||||
*
|
||||
* Unlike exceptions and asserts, these macros try to maintain consistency and stability.
|
||||
* In most cases, bugs and/or invalid data are not fatal. They should never allow a perfectly
|
||||
* running application to fail or crash.
|
||||
* Always try to return processable data, so the engine can keep running well.
|
||||
* Use the _MSG versions to print a meaningful message to help with debugging.
|
||||
*
|
||||
* The `((void)0)` no-op statement is used as a trick to force us to put a semicolon after
|
||||
* those macros, making them look like proper statements.
|
||||
* The if wrappers are used to ensure that the macro replacement does not trigger unexpected
|
||||
* issues when expanded e.g. after an `if (cond) ERR_FAIL();` without braces.
|
||||
*/
|
||||
|
||||
// Index out of bounds error macros.
|
||||
// These macros should be used instead of `ERR_FAIL_COND` for bounds checking.
|
||||
|
||||
// Integer index out of bounds error macros.
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_INDEX_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX(m_index, m_size) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, prints `m_msg` and the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX_MSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_INDEX_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX_EDMSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX_V(m_index, m_size, m_retval) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, prints `m_msg` and the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_INDEX_V_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_INDEX_MSG` or `ERR_FAIL_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and
|
||||
* there is no sensible error message.
|
||||
*
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, the application crashes.
|
||||
*/
|
||||
#define CRASH_BAD_INDEX(m_index, m_size) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), "", false, true); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_INDEX_MSG` or `ERR_FAIL_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable.
|
||||
*
|
||||
* Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0.
|
||||
* If not, prints `m_msg` and the application crashes.
|
||||
*/
|
||||
#define CRASH_BAD_INDEX_MSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, false, true); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
// Unsigned integer index out of bounds error macros.
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_UNSIGNED_INDEX_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX(m_index, m_size) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, prints `m_msg` and the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX_MSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_UNSIGNED_INDEX_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX_EDMSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_UNSIGNED_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX_V(m_index, m_size, m_retval) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, prints `m_msg` and the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_UNSIGNED_INDEX_V_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_UNSIGNED_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_UNSIGNED_INDEX_MSG` or `ERR_FAIL_UNSIGNED_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and
|
||||
* there is no sensible error message.
|
||||
*
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, the application crashes.
|
||||
*/
|
||||
#define CRASH_BAD_UNSIGNED_INDEX(m_index, m_size) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), "", false, true); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_UNSIGNED_INDEX_MSG` or `ERR_FAIL_UNSIGNED_INDEX_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable.
|
||||
*
|
||||
* Ensures an unsigned integer index `m_index` is less than `m_size`.
|
||||
* If not, prints `m_msg` and the application crashes.
|
||||
*/
|
||||
#define CRASH_BAD_UNSIGNED_INDEX_MSG(m_index, m_size, m_msg) \
|
||||
if (unlikely((m_index) >= (m_size))) { \
|
||||
_err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, false, true); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
// Null reference error macros.
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_NULL_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures a pointer `m_param` is not null.
|
||||
* If it is null, the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_NULL(m_param) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null."); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures a pointer `m_param` is not null.
|
||||
* If it is null, prints `m_msg` and the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_NULL_MSG(m_param, m_msg) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_NULL_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_NULL_EDMSG(m_param, m_msg) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg, true); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_NULL_V_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures a pointer `m_param` is not null.
|
||||
* If it is null, the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_NULL_V(m_param, m_retval) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null."); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures a pointer `m_param` is not null.
|
||||
* If it is null, prints `m_msg` and the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_NULL_V_MSG(m_param, m_retval, m_msg) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_NULL_V_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_NULL_V_EDMSG(m_param, m_retval, m_msg) \
|
||||
if (unlikely(m_param == nullptr)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg, true); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
* If checking for null use ERR_FAIL_NULL_MSG instead.
|
||||
* If checking index bounds use ERR_FAIL_INDEX_MSG instead.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_COND(m_cond) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true."); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, prints `m_msg` and the current function returns.
|
||||
*
|
||||
* If checking for null use ERR_FAIL_NULL_MSG instead.
|
||||
* If checking index bounds use ERR_FAIL_INDEX_MSG instead.
|
||||
*/
|
||||
#define ERR_FAIL_COND_MSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true.", m_msg); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_COND_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_COND_EDMSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true.", m_msg, true); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_V_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
* If checking for null use ERR_FAIL_NULL_V_MSG instead.
|
||||
* If checking index bounds use ERR_FAIL_INDEX_V_MSG instead.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_COND_V(m_cond, m_retval) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returning: " _STR(m_retval)); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, prints `m_msg` and the current function returns `m_retval`.
|
||||
*
|
||||
* If checking for null use ERR_FAIL_NULL_V_MSG instead.
|
||||
* If checking index bounds use ERR_FAIL_INDEX_V_MSG instead.
|
||||
*/
|
||||
#define ERR_FAIL_COND_V_MSG(m_cond, m_retval, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returning: " _STR(m_retval), m_msg); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_COND_V_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_COND_V_EDMSG(m_cond, m_retval, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returning: " _STR(m_retval), m_msg, true); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_CONTINUE_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, the current loop continues.
|
||||
*/
|
||||
#define ERR_CONTINUE(m_cond) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing."); \
|
||||
continue; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, prints `m_msg` and the current loop continues.
|
||||
*/
|
||||
#define ERR_CONTINUE_MSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing.", m_msg); \
|
||||
continue; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_CONTINUE_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_CONTINUE_EDMSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing.", m_msg, true); \
|
||||
continue; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_BREAK_MSG`.
|
||||
* Only use this macro if there is no sensible error message.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, the current loop breaks.
|
||||
*/
|
||||
#define ERR_BREAK(m_cond) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking."); \
|
||||
break; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, prints `m_msg` and the current loop breaks.
|
||||
*/
|
||||
#define ERR_BREAK_MSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking.", m_msg); \
|
||||
break; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_BREAK_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_BREAK_EDMSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking.", m_msg, true); \
|
||||
break; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG` or `ERR_FAIL_COND_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and
|
||||
* there is no sensible error message.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, the application crashes.
|
||||
*/
|
||||
#define CRASH_COND(m_cond) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is true."); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG` or `ERR_FAIL_COND_V_MSG`.
|
||||
* Only use this macro if there is no sensible fallback i.e. the error is unrecoverable.
|
||||
*
|
||||
* Ensures `m_cond` is false.
|
||||
* If `m_cond` is true, prints `m_msg` and the application crashes.
|
||||
*/
|
||||
#define CRASH_COND_MSG(m_cond, m_msg) \
|
||||
if (unlikely(m_cond)) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is true.", m_msg); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
// Generic error macros.
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG` or `ERR_FAIL_MSG`.
|
||||
* Only use this macro if more complex error detection or recovery is required, and
|
||||
* there is no sensible error message.
|
||||
*
|
||||
* The current function returns.
|
||||
*/
|
||||
#define ERR_FAIL() \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed."); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG`.
|
||||
* Only use this macro if more complex error detection or recovery is required.
|
||||
*
|
||||
* Prints `m_msg`, and the current function returns.
|
||||
*/
|
||||
#define ERR_FAIL_MSG(m_msg) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed.", m_msg); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_EDMSG(m_msg) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed.", m_msg, true); \
|
||||
return; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_V_MSG` or `ERR_FAIL_V_MSG`.
|
||||
* Only use this macro if more complex error detection or recovery is required, and
|
||||
* there is no sensible error message.
|
||||
*
|
||||
* The current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_V(m_retval) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed. Returning: " _STR(m_retval)); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_V_MSG`.
|
||||
* Only use this macro if more complex error detection or recovery is required.
|
||||
*
|
||||
* Prints `m_msg`, and the current function returns `m_retval`.
|
||||
*/
|
||||
#define ERR_FAIL_V_MSG(m_retval, m_msg) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed. Returning: " _STR(m_retval), m_msg); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_FAIL_V_MSG` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_FAIL_V_EDMSG(m_retval, m_msg) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed. Returning: " _STR(m_retval), m_msg, true); \
|
||||
return m_retval; \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or `ERR_BREAK_MSG`.
|
||||
* Only use this macro at the start of a function that has not been implemented yet, or
|
||||
* if more complex error detection or recovery is required.
|
||||
*
|
||||
* Prints `m_msg`.
|
||||
*/
|
||||
#define ERR_PRINT(m_msg) \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg)
|
||||
|
||||
/**
|
||||
* Same as `ERR_PRINT` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_PRINT_ED(m_msg) \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true)
|
||||
|
||||
/**
|
||||
* Prints `m_msg` once during the application lifetime.
|
||||
*/
|
||||
#define ERR_PRINT_ONCE(m_msg) \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `ERR_PRINT_ONCE` but also notifies the editor.
|
||||
*/
|
||||
#define ERR_PRINT_ONCE_ED(m_msg) \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
// Print warning message macros.
|
||||
|
||||
/**
|
||||
* Prints `m_msg`.
|
||||
*
|
||||
* If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead.
|
||||
*/
|
||||
#define WARN_PRINT(m_msg) \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, false, ERR_HANDLER_WARNING)
|
||||
|
||||
/**
|
||||
* Same as `WARN_PRINT` but also notifies the editor.
|
||||
*/
|
||||
#define WARN_PRINT_ED(m_msg) \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true, ERR_HANDLER_WARNING)
|
||||
|
||||
/**
|
||||
* Prints `m_msg` once during the application lifetime.
|
||||
*
|
||||
* If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead.
|
||||
*/
|
||||
#define WARN_PRINT_ONCE(m_msg) \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, false, ERR_HANDLER_WARNING); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Same as `WARN_PRINT_ONCE` but also notifies the editor.
|
||||
*/
|
||||
#define WARN_PRINT_ONCE_ED(m_msg) \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true, ERR_HANDLER_WARNING); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Warns about `m_msg` only when verbose mode is enabled.
|
||||
*/
|
||||
#define WARN_VERBOSE(m_msg) \
|
||||
{ \
|
||||
if (is_print_verbose_enabled()) { \
|
||||
WARN_PRINT(m_msg); \
|
||||
} \
|
||||
}
|
||||
|
||||
// Print deprecated warning message macros.
|
||||
|
||||
/**
|
||||
* Warns that the current function is deprecated.
|
||||
*/
|
||||
#define WARN_DEPRECATED \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", false, ERR_HANDLER_WARNING); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Warns that the current function is deprecated and prints `m_msg`.
|
||||
*/
|
||||
#define WARN_DEPRECATED_MSG(m_msg) \
|
||||
if (true) { \
|
||||
static bool warning_shown = false; \
|
||||
if (unlikely(!warning_shown)) { \
|
||||
warning_shown = true; \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", m_msg, false, ERR_HANDLER_WARNING); \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Do not use.
|
||||
* If the application should never reach this point use CRASH_NOW_MSG(m_msg) to explain why.
|
||||
*
|
||||
* The application crashes.
|
||||
*/
|
||||
#define CRASH_NOW() \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method/function failed."); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Only use if the application should never reach this point.
|
||||
*
|
||||
* Prints `m_msg`, and then the application crashes.
|
||||
*/
|
||||
#define CRASH_NOW_MSG(m_msg) \
|
||||
if (true) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method/function failed.", m_msg); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
|
||||
/**
|
||||
* Note: IN MOST CASES YOU SHOULD NOT USE THIS MACRO.
|
||||
* Do not use unless you understand the trade-offs.
|
||||
*
|
||||
* DEV macros will be compiled out in releases, they are wrapped in DEV_ENABLED.
|
||||
*
|
||||
* Prefer WARNINGS / ERR_FAIL macros (which fail without crashing) - ERR_FAIL should be used in most cases.
|
||||
* Then CRASH_NOW_MSG macros (on rare occasions where error cannot be recovered).
|
||||
*
|
||||
* DEV_ASSERT should generally only be used when both of the following conditions are met:
|
||||
* 1) Bottleneck code where a check in release would be too expensive.
|
||||
* 2) Situations where the check would fail obviously and straight away during the maintenance of the code
|
||||
* (i.e. strict conditions that should be true no matter what)
|
||||
* and that can't fail for other contributors once the code is finished and merged.
|
||||
*/
|
||||
#ifdef DEV_ENABLED
|
||||
#define DEV_ASSERT(m_cond) \
|
||||
if (unlikely(!(m_cond))) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: DEV_ASSERT failed \"" _STR(m_cond) "\" is false."); \
|
||||
_err_flush_stdout(); \
|
||||
GENERATE_TRAP(); \
|
||||
} else \
|
||||
((void)0)
|
||||
#else
|
||||
#define DEV_ASSERT(m_cond)
|
||||
#endif
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
#define DEV_CHECK_ONCE(m_cond) \
|
||||
if (true) { \
|
||||
static bool first_print = true; \
|
||||
if (first_print && unlikely(!(m_cond))) { \
|
||||
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \
|
||||
first_print = false; \
|
||||
} \
|
||||
} else \
|
||||
((void)0)
|
||||
#else
|
||||
#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)
|
||||
18
core/extension/SCsub
Normal file
18
core/extension/SCsub
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
import make_interface_dumper
|
||||
import make_wrappers
|
||||
|
||||
env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run))
|
||||
env.CommandNoCache(
|
||||
"gdextension_interface_dump.gen.h",
|
||||
["gdextension_interface.h", "make_interface_dumper.py"],
|
||||
env.Run(make_interface_dumper.run),
|
||||
)
|
||||
|
||||
env_extension = env.Clone()
|
||||
|
||||
env_extension.add_source_files(env.core_sources, "*.cpp")
|
||||
1679
core/extension/extension_api_dump.cpp
Normal file
1679
core/extension/extension_api_dump.cpp
Normal file
File diff suppressed because it is too large
Load Diff
43
core/extension/extension_api_dump.h
Normal file
43
core/extension/extension_api_dump.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**************************************************************************/
|
||||
/* extension_api_dump.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/gdextension.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
class GDExtensionAPIDump {
|
||||
public:
|
||||
static Dictionary generate_extension_api(bool p_include_docs = false);
|
||||
static void generate_extension_json_file(const String &p_path, bool p_include_docs = false);
|
||||
static Error validate_extension_json_file(const String &p_path);
|
||||
};
|
||||
#endif
|
||||
49
core/extension/gdextension.compat.inc
Normal file
49
core/extension/gdextension.compat.inc
Normal file
@@ -0,0 +1,49 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension.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
|
||||
|
||||
Error GDExtension::_open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
void GDExtension::_close_library_bind_compat_88418() {
|
||||
}
|
||||
|
||||
void GDExtension::_initialize_library_bind_compat_88418(InitializationLevel p_level) {
|
||||
}
|
||||
|
||||
void GDExtension::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("open_library", "path", "entry_symbol"), &GDExtension::_open_library_bind_compat_88418);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("close_library"), &GDExtension::_close_library_bind_compat_88418);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("initialize_library", "level"), &GDExtension::_initialize_library_bind_compat_88418);
|
||||
}
|
||||
|
||||
#endif
|
||||
1112
core/extension/gdextension.cpp
Normal file
1112
core/extension/gdextension.cpp
Normal file
File diff suppressed because it is too large
Load Diff
240
core/extension/gdextension.h
Normal file
240
core/extension/gdextension.h
Normal file
@@ -0,0 +1,240 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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"
|
||||
|
||||
class GDExtensionMethodBind;
|
||||
|
||||
class GDExtension : public Resource {
|
||||
GDCLASS(GDExtension, Resource)
|
||||
|
||||
friend class GDExtensionManager;
|
||||
|
||||
Ref<GDExtensionLoader> loader;
|
||||
|
||||
bool reloadable = false;
|
||||
|
||||
struct Extension {
|
||||
ObjectGDExtension gdextension;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloading = false;
|
||||
HashMap<StringName, GDExtensionMethodBind *> methods;
|
||||
HashSet<ObjectID> instances;
|
||||
|
||||
struct InstanceState {
|
||||
List<Pair<String, Variant>> properties;
|
||||
bool is_placeholder = false;
|
||||
};
|
||||
HashMap<ObjectID, InstanceState> instance_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
HashMap<StringName, Extension> extension_classes;
|
||||
|
||||
struct ClassCreationDeprecatedInfo {
|
||||
#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);
|
||||
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_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
static void _register_extension_class5(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo5 *p_extension_funcs);
|
||||
static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo5 *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);
|
||||
static void _register_extension_class_property(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter);
|
||||
static void _register_extension_class_property_indexed(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter, GDExtensionInt p_index);
|
||||
static void _register_extension_class_property_group(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_group_name, GDExtensionConstStringNamePtr p_prefix);
|
||||
static void _register_extension_class_property_subgroup(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_subgroup_name, GDExtensionConstStringNamePtr p_prefix);
|
||||
static void _register_extension_class_signal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count);
|
||||
static void _unregister_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name);
|
||||
static void _get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path);
|
||||
static void _register_get_classes_used_callback(GDExtensionClassLibraryPtr p_library, GDExtensionEditorGetClassesUsedCallback p_callback);
|
||||
static void _register_main_loop_callbacks(GDExtensionClassLibraryPtr p_library, const GDExtensionMainLoopCallbacks *p_callbacks);
|
||||
|
||||
GDExtensionInitialization initialization;
|
||||
int32_t level_initialized = -1;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloading = false;
|
||||
Vector<GDExtensionMethodBind *> invalid_methods;
|
||||
Vector<ObjectID> instance_bindings;
|
||||
GDExtensionEditorGetClassesUsedCallback get_classes_used_callback = nullptr;
|
||||
|
||||
static void _track_instance(void *p_user_data, void *p_instance);
|
||||
static void _untrack_instance(void *p_user_data, void *p_instance);
|
||||
|
||||
void _clear_extension(Extension *p_extension);
|
||||
|
||||
// Only called by GDExtensionManager during the reload process.
|
||||
void prepare_reload();
|
||||
void finish_reload();
|
||||
void clear_instance_bindings();
|
||||
#endif
|
||||
|
||||
GDExtensionMainLoopStartupCallback startup_callback = nullptr;
|
||||
GDExtensionMainLoopShutdownCallback shutdown_callback = nullptr;
|
||||
GDExtensionMainLoopFrameCallback frame_callback = nullptr;
|
||||
|
||||
static inline HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
HashMap<String, String> class_icon_paths;
|
||||
|
||||
virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way.
|
||||
|
||||
static String get_extension_list_config_file();
|
||||
|
||||
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,
|
||||
INITIALIZATION_LEVEL_SERVERS = GDEXTENSION_INITIALIZATION_SERVERS,
|
||||
INITIALIZATION_LEVEL_SCENE = GDEXTENSION_INITIALIZATION_SCENE,
|
||||
INITIALIZATION_LEVEL_EDITOR = GDEXTENSION_INITIALIZATION_EDITOR
|
||||
};
|
||||
|
||||
protected:
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
Error _open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol);
|
||||
void _close_library_bind_compat_88418();
|
||||
void _initialize_library_bind_compat_88418(InitializationLevel p_level);
|
||||
static void _bind_compatibility_methods();
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloadable() const { return reloadable; }
|
||||
void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; }
|
||||
|
||||
bool has_library_changed() const;
|
||||
|
||||
void track_instance_binding(Object *p_object);
|
||||
void untrack_instance_binding(Object *p_object);
|
||||
|
||||
PackedStringArray get_classes_used() const;
|
||||
#endif
|
||||
|
||||
InitializationLevel get_minimum_library_initialization_level() const;
|
||||
void initialize_library(InitializationLevel p_level);
|
||||
void deinitialize_library(InitializationLevel p_level);
|
||||
|
||||
static void register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer);
|
||||
static GDExtensionInterfaceFunctionPtr get_interface_function(const StringName &p_function_name);
|
||||
static void initialize_gdextensions();
|
||||
static void finalize_gdextensions();
|
||||
|
||||
GDExtension();
|
||||
~GDExtension();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(GDExtension::InitializationLevel)
|
||||
|
||||
class GDExtensionResourceLoader : public ResourceFormatLoader {
|
||||
public:
|
||||
static Error load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension);
|
||||
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
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;
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
|
||||
#endif // TOOLS_ENABLED
|
||||
};
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
class GDExtensionEditorPlugins {
|
||||
private:
|
||||
static inline Vector<StringName> extension_classes;
|
||||
|
||||
protected:
|
||||
friend class EditorNode;
|
||||
|
||||
// Since this in core, we can't directly reference EditorNode, so it will
|
||||
// set these function pointers in its constructor.
|
||||
typedef void (*EditorPluginRegisterFunc)(const StringName &p_class_name);
|
||||
static inline EditorPluginRegisterFunc editor_node_add_plugin = nullptr;
|
||||
static inline EditorPluginRegisterFunc editor_node_remove_plugin = nullptr;
|
||||
|
||||
public:
|
||||
static void add_extension_class(const StringName &p_class_name);
|
||||
static void remove_extension_class(const StringName &p_class_name);
|
||||
|
||||
static const Vector<StringName> &get_extension_classes() {
|
||||
return extension_classes;
|
||||
}
|
||||
};
|
||||
|
||||
class GDExtensionEditorHelp {
|
||||
protected:
|
||||
friend class EditorHelp;
|
||||
|
||||
// Similarly to EditorNode above, we need to be able to ask EditorHelp to parse
|
||||
// new documentation data. Note though that, differently from EditorHelp, this
|
||||
// is initialized even _before_ it gets instantiated, as we need to rely on
|
||||
// this method while initializing the engine.
|
||||
typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size);
|
||||
static inline EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer = nullptr;
|
||||
|
||||
typedef void (*EditorHelpRemoveClassFunc)(const String &p_class);
|
||||
static inline EditorHelpRemoveClassFunc editor_help_remove_class = nullptr;
|
||||
|
||||
public:
|
||||
static void load_xml_buffer(const uint8_t *p_buffer, int p_size);
|
||||
static void remove_class(const String &p_class);
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
1854
core/extension/gdextension_interface.cpp
Normal file
1854
core/extension/gdextension_interface.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3198
core/extension/gdextension_interface.h
Normal file
3198
core/extension/gdextension_interface.h
Normal file
File diff suppressed because it is too large
Load Diff
394
core/extension/gdextension_library_loader.cpp
Normal file
394
core/extension/gdextension_library_loader.cpp
Normal 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")) {
|
||||
Vector<String> config_dependencies = p_config->get_section_keys("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")) {
|
||||
Vector<String> libraries = p_config->get_section_keys("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 (GODOT_VERSION_MAJOR != compatibility_minimum[0]) {
|
||||
compatible = GODOT_VERSION_MAJOR > compatibility_minimum[0];
|
||||
} else if (GODOT_VERSION_MINOR != compatibility_minimum[1]) {
|
||||
compatible = GODOT_VERSION_MINOR > compatibility_minimum[1];
|
||||
} else {
|
||||
compatible = GODOT_VERSION_PATCH >= compatibility_minimum[2];
|
||||
}
|
||||
if (!compatible) {
|
||||
ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s, but your Godot version is %d.%d.%d",
|
||||
compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path,
|
||||
GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH));
|
||||
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 (GODOT_VERSION_MAJOR != compatibility_maximum[0]) {
|
||||
compatible = GODOT_VERSION_MAJOR < compatibility_maximum[0];
|
||||
} else if (GODOT_VERSION_MINOR != compatibility_maximum[1]) {
|
||||
compatible = GODOT_VERSION_MINOR < compatibility_maximum[1];
|
||||
}
|
||||
#if GODOT_VERSION_PATCH
|
||||
// #if check to avoid -Wtype-limits warning when 0.
|
||||
else {
|
||||
compatible = GODOT_VERSION_PATCH <= compatibility_maximum[2];
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!compatible) {
|
||||
ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s, but your Godot version is %d.%d.%d",
|
||||
compat_string, p_path, GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH));
|
||||
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")) {
|
||||
Vector<String> keys = config->get_section_keys("icons");
|
||||
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;
|
||||
}
|
||||
84
core/extension/gdextension_library_loader.h
Normal file
84
core/extension/gdextension_library_loader.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "core/extension/gdextension_loader.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/os/shared_object.h"
|
||||
|
||||
class GDExtensionLibraryLoader : public GDExtensionLoader {
|
||||
GDSOFTCLASS(GDExtensionLibraryLoader, 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);
|
||||
};
|
||||
47
core/extension/gdextension_loader.h
Normal file
47
core/extension/gdextension_loader.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class GDExtension;
|
||||
|
||||
class GDExtensionLoader : public RefCounted {
|
||||
GDSOFTCLASS(GDExtensionLoader, 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;
|
||||
};
|
||||
491
core/extension/gdextension_manager.cpp
Normal file
491
core/extension/gdextension_manager.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_manager.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_manager.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, bool p_first_load) {
|
||||
if (level >= 0) { // Already initialized up to some level.
|
||||
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++) {
|
||||
p_extension->initialize_library(GDExtension::InitializationLevel(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
|
||||
gdextension_class_icon_paths[kv.key] = kv.value;
|
||||
}
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
void GDExtensionManager::_finish_load_extension(const Ref<GDExtension> &p_extension) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Signals that a new extension is loaded so GDScript can register new class names.
|
||||
emit_signal("extension_loaded", p_extension);
|
||||
#endif
|
||||
|
||||
if (startup_callback_called) {
|
||||
// Extension is loading after the startup callback has already been called,
|
||||
// so we call it now for this extension to make sure it doesn't miss it.
|
||||
if (p_extension->startup_callback) {
|
||||
p_extension->startup_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (!shutdown_callback_called) {
|
||||
// Extension is unloading before the shutdown callback has been called,
|
||||
// which means the engine hasn't shutdown yet but we want to make sure
|
||||
// to call the shutdown callback so it doesn't miss it.
|
||||
if (p_extension->shutdown_callback) {
|
||||
p_extension->shutdown_callback();
|
||||
}
|
||||
}
|
||||
|
||||
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--) {
|
||||
p_extension->deinitialize_library(GDExtension::InitializationLevel(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
|
||||
gdextension_class_icon_paths.erase(kv.key);
|
||||
}
|
||||
|
||||
// Clear main loop callbacks.
|
||||
p_extension->startup_callback = nullptr;
|
||||
p_extension->shutdown_callback = nullptr;
|
||||
p_extension->frame_callback = nullptr;
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_finish_load_extension(extension);
|
||||
|
||||
extension->set_path(p_path);
|
||||
gdextension_map[p_path] = extension;
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String &p_path) {
|
||||
#ifndef TOOLS_ENABLED
|
||||
ERR_FAIL_V_MSG(LOAD_STATUS_FAILED, "GDExtensions can only be reloaded in an editor build.");
|
||||
#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;
|
||||
}
|
||||
|
||||
Ref<GDExtension> extension = gdextension_map[p_path];
|
||||
ERR_FAIL_COND_V_MSG(!extension->is_reloadable(), LOAD_STATUS_FAILED, vformat("This GDExtension is not marked as 'reloadable' or doesn't support reloading: %s.", p_path));
|
||||
|
||||
LoadStatus status;
|
||||
|
||||
extension->prepare_reload();
|
||||
|
||||
// Unload library if it's open. It may not be open if the developer made a
|
||||
// change that broke loading in a previous hot-reload attempt.
|
||||
if (extension->is_library_open()) {
|
||||
status = _unload_extension_internal(extension);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
// We need to clear these no matter what.
|
||||
extension->clear_instance_bindings();
|
||||
return status;
|
||||
}
|
||||
|
||||
extension->clear_instance_bindings();
|
||||
extension->close_library();
|
||||
}
|
||||
|
||||
Error err = extension->open_library(p_path, extension->loader);
|
||||
if (err != OK) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
status = _load_extension_internal(extension, false);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
extension->finish_reload();
|
||||
|
||||
// Needs to come after reload is fully finished, so all objects using
|
||||
// extension classes are in a consistent state.
|
||||
_finish_load_extension(extension);
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Ref<GDExtension> extension = gdextension_map[p_path];
|
||||
|
||||
LoadStatus status = _unload_extension_internal(extension);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
gdextension_map.erase(p_path);
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
bool GDExtensionManager::is_extension_loaded(const String &p_path) const {
|
||||
return gdextension_map.has(p_path);
|
||||
}
|
||||
|
||||
Vector<String> GDExtensionManager::get_loaded_extensions() const {
|
||||
Vector<String> ret;
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
ret.push_back(E.key);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
|
||||
HashMap<String, Ref<GDExtension>>::Iterator E = gdextension_map.find(p_path);
|
||||
ERR_FAIL_COND_V(!E, Ref<GDExtension>());
|
||||
return E->value;
|
||||
}
|
||||
|
||||
bool GDExtensionManager::class_has_icon_path(const String &p_class) const {
|
||||
// TODO: Check that the icon belongs to a registered class somehow.
|
||||
return gdextension_class_icon_paths.has(p_class);
|
||||
}
|
||||
|
||||
String GDExtensionManager::class_get_icon_path(const String &p_class) const {
|
||||
// TODO: Check that the icon belongs to a registered class somehow.
|
||||
if (gdextension_class_icon_paths.has(p_class)) {
|
||||
return gdextension_class_icon_paths[p_class];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
level = int32_t(p_level) - 1;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void GDExtensionManager::track_instance_binding(void *p_token, Object *p_object) {
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
if (E.value.ptr() == p_token) {
|
||||
if (E.value->is_reloadable()) {
|
||||
E.value->track_instance_binding(p_object);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::untrack_instance_binding(void *p_token, Object *p_object) {
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
if (E.value.ptr() == p_token) {
|
||||
if (E.value->is_reloadable()) {
|
||||
E.value->untrack_instance_binding(p_object);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::_reload_all_scripts() {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->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, vformat("Error loading extension: '%s'.", s));
|
||||
}
|
||||
}
|
||||
|
||||
OS::get_singleton()->load_platform_gdextensions();
|
||||
}
|
||||
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (E.value->has_library_changed()) {
|
||||
reloaded = true;
|
||||
reload_extension(E.value->get_path());
|
||||
}
|
||||
}
|
||||
|
||||
if (reloaded) {
|
||||
emit_signal("extensions_reloaded");
|
||||
|
||||
// Reload all scripts to clear out old references.
|
||||
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
void GDExtensionManager::startup() {
|
||||
startup_callback_called = true;
|
||||
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->startup_callback) {
|
||||
extension->startup_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::shutdown() {
|
||||
shutdown_callback_called = true;
|
||||
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->shutdown_callback) {
|
||||
extension->shutdown_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::frame() {
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->frame_callback) {
|
||||
extension->frame_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GDExtensionManager *GDExtensionManager::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
void GDExtensionManager::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension);
|
||||
ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension);
|
||||
ClassDB::bind_method(D_METHOD("unload_extension", "path"), &GDExtensionManager::unload_extension);
|
||||
ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &GDExtensionManager::is_extension_loaded);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &GDExtensionManager::get_loaded_extensions);
|
||||
ClassDB::bind_method(D_METHOD("get_extension", "path"), &GDExtensionManager::get_extension);
|
||||
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_OK);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_FAILED);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED);
|
||||
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() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDExtensionSpecialCompatHashes::initialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
GDExtensionManager::~GDExtensionManager() {
|
||||
if (singleton == this) {
|
||||
singleton = nullptr;
|
||||
}
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDExtensionSpecialCompatHashes::finalize();
|
||||
#endif
|
||||
}
|
||||
101
core/extension/gdextension_manager.h
Normal file
101
core/extension/gdextension_manager.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_manager.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/gdextension.h"
|
||||
|
||||
class GDExtensionManager : public Object {
|
||||
GDCLASS(GDExtensionManager, Object);
|
||||
|
||||
int32_t level = -1;
|
||||
HashMap<String, Ref<GDExtension>> gdextension_map;
|
||||
HashMap<String, String> gdextension_class_icon_paths;
|
||||
|
||||
bool startup_callback_called = false;
|
||||
bool shutdown_callback_called = false;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
static inline GDExtensionManager *singleton = nullptr;
|
||||
|
||||
public:
|
||||
enum LoadStatus {
|
||||
LOAD_STATUS_OK,
|
||||
LOAD_STATUS_FAILED,
|
||||
LOAD_STATUS_ALREADY_LOADED,
|
||||
LOAD_STATUS_NOT_LOADED,
|
||||
LOAD_STATUS_NEEDS_RESTART,
|
||||
};
|
||||
|
||||
private:
|
||||
LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
|
||||
void _finish_load_extension(const Ref<GDExtension> &p_extension);
|
||||
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void _reload_all_scripts();
|
||||
#endif
|
||||
|
||||
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;
|
||||
Vector<String> get_loaded_extensions() const;
|
||||
Ref<GDExtension> get_extension(const String &p_path);
|
||||
|
||||
bool class_has_icon_path(const String &p_class) const;
|
||||
String class_get_icon_path(const String &p_class) const;
|
||||
|
||||
void initialize_extensions(GDExtension::InitializationLevel p_level);
|
||||
void deinitialize_extensions(GDExtension::InitializationLevel p_level);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void track_instance_binding(void *p_token, Object *p_object);
|
||||
void untrack_instance_binding(void *p_token, Object *p_object);
|
||||
#endif
|
||||
|
||||
static GDExtensionManager *get_singleton();
|
||||
|
||||
void load_extensions();
|
||||
void reload_extensions();
|
||||
bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
|
||||
|
||||
void startup();
|
||||
void shutdown();
|
||||
void frame();
|
||||
|
||||
GDExtensionManager();
|
||||
~GDExtensionManager();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(GDExtensionManager::LoadStatus)
|
||||
1026
core/extension/gdextension_special_compat_hashes.cpp
Normal file
1026
core/extension/gdextension_special_compat_hashes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
59
core/extension/gdextension_special_compat_hashes.h
Normal file
59
core/extension/gdextension_special_compat_hashes.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_special_compat_hashes.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
// 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;
|
||||
uint32_t current_hash;
|
||||
};
|
||||
|
||||
static inline HashMap<StringName, LocalVector<Mapping>> mappings;
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
static void finalize();
|
||||
static bool lookup_current_hash(const StringName &p_class, const StringName &p_method, uint32_t p_legacy_hash, uint32_t *r_current_hash);
|
||||
static bool get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid = true);
|
||||
};
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
37
core/extension/make_interface_dumper.py
Normal file
37
core/extension/make_interface_dumper.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import methods
|
||||
|
||||
|
||||
def run(target, source, env):
|
||||
buffer = methods.get_buffer(str(source[0]))
|
||||
decomp_size = len(buffer)
|
||||
buffer = methods.compress_buffer(buffer)
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(f"""\
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
inline constexpr int _gdextension_interface_data_compressed_size = {len(buffer)};
|
||||
inline constexpr int _gdextension_interface_data_uncompressed_size = {decomp_size};
|
||||
inline constexpr unsigned char _gdextension_interface_data_compressed[] = {{
|
||||
{methods.format_buffer(buffer, 1)}
|
||||
}};
|
||||
|
||||
class GDExtensionInterfaceDump {{
|
||||
public:
|
||||
static void generate_gdextension_interface_file(const String &p_path) {{
|
||||
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
|
||||
Vector<uint8_t> data;
|
||||
data.resize(_gdextension_interface_data_uncompressed_size);
|
||||
int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
|
||||
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
|
||||
fa->store_buffer(data.ptr(), data.size());
|
||||
}};
|
||||
}};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
""")
|
||||
139
core/extension/make_wrappers.py
Normal file
139
core/extension/make_wrappers.py
Normal file
@@ -0,0 +1,139 @@
|
||||
proto_mod = """
|
||||
#define MODBIND$VER($RETTYPE m_name$ARG) \\
|
||||
virtual $RETVAL _##m_name($FUNCARGS) $CONST; \\
|
||||
_FORCE_INLINE_ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
|
||||
$RETX _##m_name($CALLARGS);\\
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate_mod_version(argcount, const=False, returns=False):
|
||||
s = proto_mod
|
||||
sproto = str(argcount)
|
||||
if returns:
|
||||
sproto += "R"
|
||||
s = s.replace("$RETTYPE", "m_ret, ")
|
||||
s = s.replace("$RETVAL", "m_ret")
|
||||
s = s.replace("$RETX", "return")
|
||||
|
||||
else:
|
||||
s = s.replace("$RETTYPE", "")
|
||||
s = s.replace("$RETVAL", "void")
|
||||
s = s.replace("$RETX", "")
|
||||
|
||||
if const:
|
||||
sproto += "C"
|
||||
s = s.replace("$CONST", "const")
|
||||
else:
|
||||
s = s.replace("$CONST", "")
|
||||
|
||||
s = s.replace("$VER", sproto)
|
||||
argtext = ""
|
||||
funcargs = ""
|
||||
callargs = ""
|
||||
|
||||
for i in range(argcount):
|
||||
if i > 0:
|
||||
funcargs += ", "
|
||||
callargs += ", "
|
||||
|
||||
argtext += ", m_type" + str(i + 1)
|
||||
funcargs += "m_type" + str(i + 1) + " arg" + str(i + 1)
|
||||
callargs += "arg" + str(i + 1)
|
||||
|
||||
if argcount:
|
||||
s = s.replace("$ARG", argtext)
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
else:
|
||||
s = s.replace("$ARG", "")
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
proto_ex = """
|
||||
#define EXBIND$VER($RETTYPE m_name$ARG) \\
|
||||
GDVIRTUAL$VER_REQUIRED($RETTYPE_##m_name$ARG)\\
|
||||
virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
|
||||
$RETPRE\\
|
||||
GDVIRTUAL_CALL(_##m_name$CALLARGS$RETREF);\\
|
||||
$RETPOST\\
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate_ex_version(argcount, const=False, returns=False):
|
||||
s = proto_ex
|
||||
sproto = str(argcount)
|
||||
if returns:
|
||||
sproto += "R"
|
||||
s = s.replace("$RETTYPE", "m_ret, ")
|
||||
s = s.replace("$RETVAL", "m_ret")
|
||||
s = s.replace("$RETPRE", "m_ret ret; ZeroInitializer<m_ret>::initialize(ret);\\\n")
|
||||
s = s.replace("$RETPOST", "return ret;\\\n")
|
||||
|
||||
else:
|
||||
s = s.replace("$RETTYPE", "")
|
||||
s = s.replace("$RETVAL", "void")
|
||||
s = s.replace("$RETPRE", "")
|
||||
s = s.replace("$RETPOST", "return;")
|
||||
|
||||
if const:
|
||||
sproto += "C"
|
||||
s = s.replace("$CONST", "const")
|
||||
else:
|
||||
s = s.replace("$CONST", "")
|
||||
|
||||
s = s.replace("$VER", sproto)
|
||||
argtext = ""
|
||||
funcargs = ""
|
||||
callargs = ""
|
||||
|
||||
for i in range(argcount):
|
||||
if i > 0:
|
||||
funcargs += ", "
|
||||
|
||||
argtext += ", m_type" + str(i + 1)
|
||||
funcargs += "m_type" + str(i + 1) + " arg" + str(i + 1)
|
||||
callargs += ", arg" + str(i + 1)
|
||||
|
||||
if argcount:
|
||||
s = s.replace("$ARG", argtext)
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
else:
|
||||
s = s.replace("$ARG", "")
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
|
||||
if returns:
|
||||
s = s.replace("$RETREF", ", ret")
|
||||
else:
|
||||
s = s.replace("$RETREF", "")
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def run(target, source, env):
|
||||
max_versions = 12
|
||||
|
||||
txt = "#pragma once"
|
||||
|
||||
for i in range(max_versions + 1):
|
||||
txt += "\n/* Extension Wrapper " + str(i) + " Arguments */\n"
|
||||
txt += generate_ex_version(i, False, False)
|
||||
txt += generate_ex_version(i, False, True)
|
||||
txt += generate_ex_version(i, True, False)
|
||||
txt += generate_ex_version(i, True, True)
|
||||
|
||||
for i in range(max_versions + 1):
|
||||
txt += "\n/* Module Wrapper " + str(i) + " Arguments */\n"
|
||||
txt += generate_mod_version(i, False, False)
|
||||
txt += generate_mod_version(i, False, True)
|
||||
txt += generate_mod_version(i, True, False)
|
||||
txt += generate_mod_version(i, True, True)
|
||||
|
||||
with open(str(target[0]), "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(txt)
|
||||
21
core/input/SCsub
Normal file
21
core/input/SCsub
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
import input_builders
|
||||
|
||||
# Order matters here. Higher index controller database files write on top of lower index database files.
|
||||
controller_databases = [
|
||||
"gamecontrollerdb.txt",
|
||||
"godotcontrollerdb.txt",
|
||||
]
|
||||
|
||||
gensource = env.CommandNoCache(
|
||||
"default_controller_mappings.gen.cpp",
|
||||
controller_databases,
|
||||
env.Run(input_builders.make_default_controller_mappings),
|
||||
)
|
||||
|
||||
env.add_source_files(env.core_sources, "*.cpp")
|
||||
env.add_source_files(env.core_sources, gensource)
|
||||
36
core/input/default_controller_mappings.h
Normal file
36
core/input/default_controller_mappings.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/**************************************************************************/
|
||||
/* default_controller_mappings.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
class DefaultControllerMappings {
|
||||
public:
|
||||
static const char *mappings[];
|
||||
};
|
||||
2166
core/input/gamecontrollerdb.txt
Normal file
2166
core/input/gamecontrollerdb.txt
Normal file
File diff suppressed because it is too large
Load Diff
47
core/input/godotcontrollerdb.txt
Normal file
47
core/input/godotcontrollerdb.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
# Game Controller DB for Godot in SDL 2.0.16 format
|
||||
# Source: https://github.com/godotengine/godot
|
||||
|
||||
# Windows
|
||||
__XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpdown:b1,dpleft:b2,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Windows,
|
||||
|
||||
# Android
|
||||
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,
|
||||
58626f7820576972656c65737320436f,Xbox Series X Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e04000091020000ff073f00,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e04000091020000ff073f80,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e040000e00200000ffe3f00,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e040000e00200000ffe3f80,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e040000e0020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e040000e0020000ffff3f80,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
050000005e040000fd020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
33356661323266333733373865656366,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
34356136633366613530316338376136,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
35623965373264386238353433656138,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
36616131643361333337396261666433,Xbox One Controller,a:b0,b:b1,back:b4,misc1:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:+a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
|
||||
|
||||
# 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,
|
||||
Linux045e02d1,Microsoft X-Box One 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,
|
||||
Linux045e02ea,Microsoft X-Box One S 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,
|
||||
Linux045e0b12,Microsoft X-Box Series X 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,
|
||||
Linux044fb315,Thrustmaster dual analog 3.2,a:b0,b:b2,y:b3,x:b1,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:-a5,dpleft:-a4,dpdown:+a5,dpright:+a4,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Web,
|
||||
Linux0e8f0003,PS3 Controller,a:b2,b:b1,back:b8,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Web,
|
||||
MacOSX24c6581a,PowerA Xbox One Cabled,a:b11,b:b12,y:b14,x:b13,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpleft:b2,dpdown:b1,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Web
|
||||
MacOSX045e028e,Xbox 360 Wired Controller,a:b11,b:b12,y:b14,x:b13,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpleft:b2,dpdown:b1,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Web
|
||||
MacOSX045e02d1,Xbox One Controller,a:b11,b:b12,y:b14,x:b13,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpleft:b2,dpdown:b1,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Web
|
||||
MacOSX045e02ea,Xbox One S Controller,a:b11,b:b12,y:b14,x:b13,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpleft:b2,dpdown:b1,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Web
|
||||
MacOSX045e0b12,Xbox Series X Controller,a:b11,b:b12,y:b14,x:b13,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpleft:b2,dpdown:b1,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Web
|
||||
Linux15320a14,Razer Wolverine Ultimate,a:b0,b:b1,y:b3,x:b2,start:b7,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
|
||||
Linux05832060,iBuffalo BSGP801,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpleft:-a0,dpdown:+a1,dpright:+a0,platform:Web
|
||||
MacOSX05832060,iBuffalo BSGP801,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpleft:-a0,dpdown:+a1,dpright:+a0,platform:Web
|
||||
Linux0e8f3013,HuiJia USB GamePad,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftshoulder:b6,rightshoulder:b7,dpup:-a1,dpleft:-a0,dpdown:+a1,dpright:+a0,platform:Web
|
||||
Windows0e8f3013,HuiJia USB GamePad,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftshoulder:b6,rightshoulder:b7,dpup:-a1,dpleft:-a0,dpdown:+a1,dpright:+a0,platform:Web
|
||||
MacOSX0e8f3013,HuiJia USB GamePad,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftshoulder:b6,rightshoulder:b7,dpup:-a4,dpleft:-a3,dpdown:+a4,dpright:+a3,platform:Web
|
||||
Linux046dc216,046d-c216-Logitech Logitech Dual Action,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:-a5,dpleft:-a4,dpdown:+a5,dpright:+a4,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Web
|
||||
Linux20d6a713,Bensussen Deutsch & Associates Inc.(BDA) NSW Wired controller,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:-a5,dpleft:-a4,dpdown:+a5,dpright:+a4,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Web
|
||||
Linux054c05c4,Sony Computer Entertainment Wireless Controller,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftstick:b11,rightstick:b12,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
|
||||
Linux18d19400,18d1-9400-Google LLC Stadia Controller rev. A,a:b0,b:b1,y:b3,x:b2,start:b7,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,platform:Web
|
||||
Linux054c0268,054c-0268-Sony PLAYSTATION(R)3 Controller,a:b0,b:b1,y:b2,x:b3,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web
|
||||
41
core/input/input.compat.inc
Normal file
41
core/input/input.compat.inc
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* input.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
|
||||
|
||||
void Input::_vibrate_handheld_bind_compat_91143(int p_duration_ms) {
|
||||
vibrate_handheld(p_duration_ms, -1.0);
|
||||
}
|
||||
|
||||
void Input::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::_vibrate_handheld_bind_compat_91143, DEFVAL(500));
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
1976
core/input/input.cpp
Normal file
1976
core/input/input.cpp
Normal file
File diff suppressed because it is too large
Load Diff
406
core/input/input.h
Normal file
406
core/input/input.h
Normal file
@@ -0,0 +1,406 @@
|
||||
/**************************************************************************/
|
||||
/* input.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/templates/rb_set.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
class Input : public Object {
|
||||
GDCLASS(Input, Object);
|
||||
_THREAD_SAFE_CLASS_
|
||||
|
||||
static inline Input *singleton = nullptr;
|
||||
|
||||
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
|
||||
enum CursorShape {
|
||||
CURSOR_ARROW,
|
||||
CURSOR_IBEAM,
|
||||
CURSOR_POINTING_HAND,
|
||||
CURSOR_CROSS,
|
||||
CURSOR_WAIT,
|
||||
CURSOR_BUSY,
|
||||
CURSOR_DRAG,
|
||||
CURSOR_CAN_DROP,
|
||||
CURSOR_FORBIDDEN,
|
||||
CURSOR_VSIZE,
|
||||
CURSOR_HSIZE,
|
||||
CURSOR_BDIAGSIZE,
|
||||
CURSOR_FDIAGSIZE,
|
||||
CURSOR_MOVE,
|
||||
CURSOR_VSPLIT,
|
||||
CURSOR_HSPLIT,
|
||||
CURSOR_HELP,
|
||||
CURSOR_MAX
|
||||
};
|
||||
|
||||
static constexpr int32_t JOYPADS_MAX = 16;
|
||||
|
||||
typedef void (*EventDispatchFunc)(const Ref<InputEvent> &p_event);
|
||||
|
||||
private:
|
||||
BitField<MouseButtonMask> mouse_button_mask = MouseButtonMask::NONE;
|
||||
|
||||
RBSet<Key> key_label_pressed;
|
||||
RBSet<Key> physical_keys_pressed;
|
||||
RBSet<Key> keys_pressed;
|
||||
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;
|
||||
uint64_t pressed_process_frame = UINT64_MAX;
|
||||
uint64_t released_physics_frame = UINT64_MAX;
|
||||
uint64_t released_process_frame = UINT64_MAX;
|
||||
ObjectID pressed_event_id;
|
||||
ObjectID released_event_id;
|
||||
bool exact = true;
|
||||
|
||||
struct DeviceState {
|
||||
bool pressed[MAX_EVENT] = { false };
|
||||
float strength[MAX_EVENT] = { 0.0 };
|
||||
float raw_strength[MAX_EVENT] = { 0.0 };
|
||||
};
|
||||
bool api_pressed = false;
|
||||
float api_strength = 0.0;
|
||||
HashMap<int, DeviceState> device_states;
|
||||
|
||||
// Cache.
|
||||
struct ActionStateCache {
|
||||
bool pressed = false;
|
||||
float strength = false;
|
||||
float raw_strength = false;
|
||||
} cache;
|
||||
};
|
||||
|
||||
HashMap<StringName, ActionState> action_states;
|
||||
|
||||
bool emulate_touch_from_mouse = false;
|
||||
bool emulate_mouse_from_touch = false;
|
||||
bool agile_input_event_flushing = false;
|
||||
bool use_accumulated_input = true;
|
||||
|
||||
int mouse_from_touch_index = -1;
|
||||
|
||||
struct VibrationInfo {
|
||||
float weak_magnitude;
|
||||
float strong_magnitude;
|
||||
float duration; // Duration in seconds
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
HashMap<int, VibrationInfo> joy_vibration;
|
||||
|
||||
struct VelocityTrack {
|
||||
uint64_t last_tick = 0;
|
||||
Vector2 velocity;
|
||||
Vector2 screen_velocity;
|
||||
Vector2 accum;
|
||||
Vector2 screen_accum;
|
||||
float accum_t = 0.0f;
|
||||
float min_ref_frame;
|
||||
float max_ref_frame;
|
||||
|
||||
void update(const Vector2 &p_delta_p, const Vector2 &p_screen_delta_p);
|
||||
void reset();
|
||||
VelocityTrack();
|
||||
};
|
||||
|
||||
struct Joypad {
|
||||
StringName name;
|
||||
StringName uid;
|
||||
bool connected = false;
|
||||
bool last_buttons[(size_t)JoyButton::MAX] = { false };
|
||||
float last_axis[(size_t)JoyAxis::MAX] = { 0.0f };
|
||||
HatMask last_hat = HatMask::CENTER;
|
||||
int mapping = -1;
|
||||
int hat_current = 0;
|
||||
Dictionary info;
|
||||
};
|
||||
|
||||
VelocityTrack mouse_velocity_track;
|
||||
HashMap<int, VelocityTrack> touch_velocity_track;
|
||||
HashMap<int, Joypad> joy_names;
|
||||
|
||||
HashSet<uint32_t> ignored_device_ids;
|
||||
|
||||
int fallback_mapping = -1; // Index of the guid in map_db.
|
||||
|
||||
CursorShape default_shape = CURSOR_ARROW;
|
||||
|
||||
enum JoyType {
|
||||
TYPE_BUTTON,
|
||||
TYPE_AXIS,
|
||||
TYPE_HAT,
|
||||
TYPE_MAX,
|
||||
};
|
||||
|
||||
enum JoyAxisRange {
|
||||
NEGATIVE_HALF_AXIS = -1,
|
||||
FULL_AXIS = 0,
|
||||
POSITIVE_HALF_AXIS = 1
|
||||
};
|
||||
|
||||
struct JoyEvent {
|
||||
int type = TYPE_MAX;
|
||||
int index = -1; // Can be either JoyAxis or JoyButton.
|
||||
float value = 0.f;
|
||||
};
|
||||
|
||||
struct JoyBinding {
|
||||
JoyType inputType;
|
||||
union {
|
||||
JoyButton button;
|
||||
|
||||
struct {
|
||||
JoyAxis axis;
|
||||
JoyAxisRange range;
|
||||
bool invert;
|
||||
} axis;
|
||||
|
||||
struct {
|
||||
HatDir hat;
|
||||
HatMask hat_mask;
|
||||
} hat;
|
||||
|
||||
} input;
|
||||
|
||||
JoyType outputType;
|
||||
union {
|
||||
JoyButton button;
|
||||
|
||||
struct {
|
||||
JoyAxis axis;
|
||||
JoyAxisRange range;
|
||||
} axis;
|
||||
|
||||
} output;
|
||||
};
|
||||
|
||||
struct JoyDeviceMapping {
|
||||
String uid;
|
||||
String name;
|
||||
Vector<JoyBinding> bindings;
|
||||
};
|
||||
|
||||
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]);
|
||||
JoyButton _get_output_button(const String &output);
|
||||
JoyAxis _get_output_axis(const String &output);
|
||||
void _button_event(int p_device, JoyButton p_index, bool p_pressed);
|
||||
void _axis_event(int p_device, JoyAxis p_axis, float p_value);
|
||||
void _update_action_cache(const StringName &p_action_name, ActionState &r_action_state);
|
||||
|
||||
void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated);
|
||||
|
||||
List<Ref<InputEvent>> buffered_events;
|
||||
#ifdef DEBUG_ENABLED
|
||||
HashSet<Ref<InputEvent>> frame_parsed_events;
|
||||
uint64_t last_parsed_frame = UINT64_MAX;
|
||||
#endif
|
||||
|
||||
friend class DisplayServer;
|
||||
|
||||
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)();
|
||||
static void (*set_custom_mouse_cursor_func)(const Ref<Resource> &, CursorShape, const Vector2 &);
|
||||
|
||||
EventDispatchFunc event_dispatch_function = nullptr;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _vibrate_handheld_bind_compat_91143(int p_duration_ms = 500);
|
||||
static void _bind_compatibility_methods();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
#endif
|
||||
|
||||
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;
|
||||
bool is_mouse_button_pressed(MouseButton p_button) const;
|
||||
bool is_joy_button_pressed(int p_device, JoyButton p_button) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_just_pressed(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_just_released(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_just_pressed_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact = false) const;
|
||||
bool is_action_just_released_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact = false) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact = false) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact = false) const;
|
||||
|
||||
float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const;
|
||||
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const;
|
||||
|
||||
float get_joy_axis(int p_device, JoyAxis p_axis) const;
|
||||
String get_joy_name(int p_idx);
|
||||
TypedArray<int> get_connected_joypads();
|
||||
Vector2 get_joy_vibration_strength(int p_device);
|
||||
float get_joy_vibration_duration(int p_device);
|
||||
uint64_t get_joy_vibration_timestamp(int p_device);
|
||||
void joy_connection_changed(int p_idx, bool p_connected, const String &p_name, const String &p_guid = "", const Dictionary &p_joypad_info = Dictionary());
|
||||
|
||||
Vector3 get_gravity() const;
|
||||
Vector3 get_accelerometer() const;
|
||||
Vector3 get_magnetometer() const;
|
||||
Vector3 get_gyroscope() const;
|
||||
|
||||
Point2 get_mouse_position() const;
|
||||
Vector2 get_last_mouse_velocity();
|
||||
Vector2 get_last_mouse_screen_velocity();
|
||||
BitField<MouseButtonMask> get_mouse_button_mask() const;
|
||||
|
||||
void warp_mouse(const Vector2 &p_position);
|
||||
Point2 warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect);
|
||||
|
||||
void parse_input_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
void set_gravity(const Vector3 &p_gravity);
|
||||
void set_accelerometer(const Vector3 &p_accel);
|
||||
void set_magnetometer(const Vector3 &p_magnetometer);
|
||||
void set_gyroscope(const Vector3 &p_gyroscope);
|
||||
void set_joy_axis(int p_device, JoyAxis p_axis, float p_value);
|
||||
|
||||
void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0);
|
||||
void stop_joy_vibration(int p_device);
|
||||
void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0);
|
||||
|
||||
void set_mouse_position(const Point2 &p_posf);
|
||||
|
||||
void action_press(const StringName &p_action, float p_strength = 1.f);
|
||||
void action_release(const StringName &p_action);
|
||||
|
||||
void set_emulate_touch_from_mouse(bool p_emulate);
|
||||
bool is_emulating_touch_from_mouse() const;
|
||||
void ensure_touch_mouse_raised();
|
||||
|
||||
void set_emulate_mouse_from_touch(bool p_emulate);
|
||||
bool is_emulating_mouse_from_touch() const;
|
||||
|
||||
CursorShape get_default_cursor_shape() const;
|
||||
void set_default_cursor_shape(CursorShape p_shape);
|
||||
CursorShape get_current_cursor_shape() const;
|
||||
void set_custom_mouse_cursor(const Ref<Resource> &p_cursor, CursorShape p_shape = Input::CURSOR_ARROW, const Vector2 &p_hotspot = Vector2());
|
||||
|
||||
void parse_mapping(const String &p_mapping);
|
||||
void joy_button(int p_device, JoyButton p_button, bool p_pressed);
|
||||
void joy_axis(int p_device, JoyAxis p_axis, float p_value);
|
||||
void joy_hat(int p_device, BitField<HatMask> p_val);
|
||||
|
||||
void add_joy_mapping(const String &p_mapping, bool p_update_existing = false);
|
||||
void remove_joy_mapping(const String &p_guid);
|
||||
|
||||
int get_unused_joy_id();
|
||||
|
||||
bool is_joy_known(int p_device);
|
||||
String get_joy_guid(int p_device) const;
|
||||
bool should_ignore_device(int p_vendor_id, int p_product_id) const;
|
||||
Dictionary get_joy_info(int p_device) const;
|
||||
void set_fallback_mapping(const String &p_guid);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void flush_frame_parsed_events();
|
||||
#endif
|
||||
void flush_buffered_events();
|
||||
bool is_agile_input_event_flushing();
|
||||
void set_agile_input_event_flushing(bool p_enable);
|
||||
void set_use_accumulated_input(bool p_enable);
|
||||
bool is_using_accumulated_input();
|
||||
|
||||
void release_pressed_events();
|
||||
|
||||
void set_event_dispatch_function(EventDispatchFunc p_function);
|
||||
|
||||
void set_disable_input(bool p_disable);
|
||||
bool is_input_disabled() const;
|
||||
|
||||
Input();
|
||||
~Input();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Input::MouseMode);
|
||||
VARIANT_ENUM_CAST(Input::CursorShape);
|
||||
63
core/input/input_builders.py
Normal file
63
core/input/input_builders.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Functions used to generate source files during build time"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import methods
|
||||
|
||||
|
||||
def make_default_controller_mappings(target, source, env):
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write("""\
|
||||
#include "core/input/default_controller_mappings.h"
|
||||
|
||||
#include "core/typedefs.h"
|
||||
|
||||
""")
|
||||
|
||||
# ensure mappings have a consistent order
|
||||
platform_mappings = OrderedDict()
|
||||
for src_path in map(str, source):
|
||||
with open(src_path, "r", encoding="utf-8") as f:
|
||||
# read mapping file and skip header
|
||||
mapping_file_lines = f.readlines()[2:]
|
||||
|
||||
current_platform = None
|
||||
for line in mapping_file_lines:
|
||||
if not line:
|
||||
continue
|
||||
line = line.strip()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
if line[0] == "#":
|
||||
current_platform = line[1:].strip()
|
||||
if current_platform not in platform_mappings:
|
||||
platform_mappings[current_platform] = {}
|
||||
elif current_platform:
|
||||
line_parts = line.split(",")
|
||||
guid = line_parts[0]
|
||||
if guid in platform_mappings[current_platform]:
|
||||
file.write(
|
||||
"// WARNING: DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format(
|
||||
src_path, current_platform, platform_mappings[current_platform][guid]
|
||||
)
|
||||
)
|
||||
platform_mappings[current_platform][guid] = line
|
||||
|
||||
PLATFORM_VARIABLES = {
|
||||
"Linux": "LINUXBSD",
|
||||
"Windows": "WINDOWS",
|
||||
"Mac OS X": "MACOS",
|
||||
"Android": "ANDROID",
|
||||
"iOS": "APPLE_EMBEDDED",
|
||||
"Web": "WEB",
|
||||
}
|
||||
|
||||
file.write("const char *DefaultControllerMappings::mappings[] = {\n")
|
||||
for platform, mappings in platform_mappings.items():
|
||||
variable = PLATFORM_VARIABLES[platform]
|
||||
file.write(f"#ifdef {variable}_ENABLED\n")
|
||||
for mapping in mappings.values():
|
||||
file.write(f'\t"{mapping}",\n')
|
||||
file.write(f"#endif // {variable}_ENABLED\n")
|
||||
|
||||
file.write("\tnullptr\n};\n")
|
||||
163
core/input/input_enums.h
Normal file
163
core/input/input_enums.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/**************************************************************************/
|
||||
/* input_enums.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/error/error_macros.h"
|
||||
|
||||
enum class InputEventType {
|
||||
INVALID = -1,
|
||||
KEY,
|
||||
MOUSE_BUTTON,
|
||||
MOUSE_MOTION,
|
||||
JOY_MOTION,
|
||||
JOY_BUTTON,
|
||||
SCREEN_TOUCH,
|
||||
SCREEN_DRAG,
|
||||
MAGNIFY_GESTURE,
|
||||
PAN_GESTURE,
|
||||
MIDI,
|
||||
SHORTCUT,
|
||||
ACTION,
|
||||
MAX,
|
||||
};
|
||||
|
||||
enum class HatDir {
|
||||
UP = 0,
|
||||
RIGHT = 1,
|
||||
DOWN = 2,
|
||||
LEFT = 3,
|
||||
MAX = 4,
|
||||
};
|
||||
|
||||
enum class HatMask {
|
||||
CENTER = 0,
|
||||
UP = 1,
|
||||
RIGHT = 2,
|
||||
DOWN = 4,
|
||||
LEFT = 8,
|
||||
};
|
||||
|
||||
enum class JoyAxis {
|
||||
INVALID = -1,
|
||||
LEFT_X = 0,
|
||||
LEFT_Y = 1,
|
||||
RIGHT_X = 2,
|
||||
RIGHT_Y = 3,
|
||||
TRIGGER_LEFT = 4,
|
||||
TRIGGER_RIGHT = 5,
|
||||
SDL_MAX = 6,
|
||||
MAX = 10, // OpenVR supports up to 5 Joysticks making a total of 10 axes.
|
||||
};
|
||||
|
||||
enum class JoyButton {
|
||||
INVALID = -1,
|
||||
A = 0,
|
||||
B = 1,
|
||||
X = 2,
|
||||
Y = 3,
|
||||
BACK = 4,
|
||||
GUIDE = 5,
|
||||
START = 6,
|
||||
LEFT_STICK = 7,
|
||||
RIGHT_STICK = 8,
|
||||
LEFT_SHOULDER = 9,
|
||||
RIGHT_SHOULDER = 10,
|
||||
DPAD_UP = 11,
|
||||
DPAD_DOWN = 12,
|
||||
DPAD_LEFT = 13,
|
||||
DPAD_RIGHT = 14,
|
||||
MISC1 = 15,
|
||||
PADDLE1 = 16,
|
||||
PADDLE2 = 17,
|
||||
PADDLE3 = 18,
|
||||
PADDLE4 = 19,
|
||||
TOUCHPAD = 20,
|
||||
SDL_MAX = 21,
|
||||
MAX = 128, // Android supports up to 36 buttons. DirectInput supports up to 128 buttons.
|
||||
};
|
||||
|
||||
enum class MIDIMessage {
|
||||
NONE = 0,
|
||||
NOTE_OFF = 0x8,
|
||||
NOTE_ON = 0x9,
|
||||
AFTERTOUCH = 0xA,
|
||||
CONTROL_CHANGE = 0xB,
|
||||
PROGRAM_CHANGE = 0xC,
|
||||
CHANNEL_PRESSURE = 0xD,
|
||||
PITCH_BEND = 0xE,
|
||||
SYSTEM_EXCLUSIVE = 0xF0,
|
||||
QUARTER_FRAME = 0xF1,
|
||||
SONG_POSITION_POINTER = 0xF2,
|
||||
SONG_SELECT = 0xF3,
|
||||
TUNE_REQUEST = 0xF6,
|
||||
TIMING_CLOCK = 0xF8,
|
||||
START = 0xFA,
|
||||
CONTINUE = 0xFB,
|
||||
STOP = 0xFC,
|
||||
ACTIVE_SENSING = 0xFE,
|
||||
SYSTEM_RESET = 0xFF,
|
||||
};
|
||||
|
||||
enum class MouseButton {
|
||||
NONE = 0,
|
||||
LEFT = 1,
|
||||
RIGHT = 2,
|
||||
MIDDLE = 3,
|
||||
WHEEL_UP = 4,
|
||||
WHEEL_DOWN = 5,
|
||||
WHEEL_LEFT = 6,
|
||||
WHEEL_RIGHT = 7,
|
||||
MB_XBUTTON1 = 8, // "XBUTTON1" is a reserved word on Windows.
|
||||
MB_XBUTTON2 = 9, // "XBUTTON2" is a reserved word on Windows.
|
||||
};
|
||||
|
||||
enum class MouseButtonMask {
|
||||
NONE = 0,
|
||||
LEFT = (1 << (int(MouseButton::LEFT) - 1)),
|
||||
RIGHT = (1 << (int(MouseButton::RIGHT) - 1)),
|
||||
MIDDLE = (1 << (int(MouseButton::MIDDLE) - 1)),
|
||||
MB_XBUTTON1 = (1 << (int(MouseButton::MB_XBUTTON1) - 1)),
|
||||
MB_XBUTTON2 = (1 << (int(MouseButton::MB_XBUTTON2) - 1)),
|
||||
};
|
||||
|
||||
inline MouseButtonMask mouse_button_to_mask(MouseButton button) {
|
||||
ERR_FAIL_COND_V(button == MouseButton::NONE, MouseButtonMask::NONE);
|
||||
|
||||
return MouseButtonMask(1 << ((int)button - 1));
|
||||
}
|
||||
|
||||
constexpr MouseButtonMask operator|(MouseButtonMask p_a, MouseButtonMask p_b) {
|
||||
return static_cast<MouseButtonMask>(static_cast<int>(p_a) | static_cast<int>(p_b));
|
||||
}
|
||||
|
||||
constexpr MouseButtonMask &operator|=(MouseButtonMask &p_a, MouseButtonMask p_b) {
|
||||
return p_a = p_a | p_b;
|
||||
}
|
||||
1935
core/input/input_event.cpp
Normal file
1935
core/input/input_event.cpp
Normal file
File diff suppressed because it is too large
Load Diff
622
core/input/input_event.h
Normal file
622
core/input/input_event.h
Normal file
@@ -0,0 +1,622 @@
|
||||
/**************************************************************************/
|
||||
/* input_event.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_enums.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "core/math/transform_2d.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
/**
|
||||
* Input Event classes. These are used in the main loop.
|
||||
* The events are pretty obvious.
|
||||
*/
|
||||
|
||||
class Shortcut;
|
||||
|
||||
/**
|
||||
* Input Modifier Status
|
||||
* for keyboard/mouse events.
|
||||
*/
|
||||
|
||||
class InputEvent : public Resource {
|
||||
GDCLASS(InputEvent, Resource);
|
||||
|
||||
int device = 0;
|
||||
|
||||
protected:
|
||||
bool canceled = false;
|
||||
bool pressed = false;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static constexpr int DEVICE_ID_EMULATION = -1;
|
||||
static constexpr int DEVICE_ID_INTERNAL = -2;
|
||||
|
||||
void set_device(int p_device);
|
||||
int get_device() const;
|
||||
|
||||
bool is_action(const StringName &p_action, bool p_exact_match = false) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false) const;
|
||||
bool is_action_released(const StringName &p_action, bool p_exact_match = false) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact_match = false) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const;
|
||||
|
||||
bool is_canceled() const;
|
||||
bool is_pressed() const;
|
||||
bool is_released() const;
|
||||
virtual bool is_echo() const;
|
||||
|
||||
virtual String as_text() const = 0;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const;
|
||||
|
||||
virtual bool is_action_type() const;
|
||||
|
||||
virtual bool accumulate(const Ref<InputEvent> &p_event) { return false; }
|
||||
|
||||
virtual InputEventType get_type() const { return InputEventType::INVALID; }
|
||||
|
||||
InputEvent() {}
|
||||
};
|
||||
|
||||
class InputEventFromWindow : public InputEvent {
|
||||
GDCLASS(InputEventFromWindow, InputEvent);
|
||||
|
||||
int64_t window_id = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_window_id(int64_t p_id);
|
||||
int64_t get_window_id() const;
|
||||
|
||||
InputEventFromWindow() {}
|
||||
};
|
||||
|
||||
class InputEventWithModifiers : public InputEventFromWindow {
|
||||
GDCLASS(InputEventWithModifiers, InputEventFromWindow);
|
||||
|
||||
bool command_or_control_autoremap = false;
|
||||
|
||||
bool shift_pressed = false;
|
||||
bool alt_pressed = false;
|
||||
bool meta_pressed = false; // "Command" on macOS, "Meta/Win" key on other platforms.
|
||||
bool ctrl_pressed = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
public:
|
||||
void set_command_or_control_autoremap(bool p_enabled);
|
||||
bool is_command_or_control_autoremap() const;
|
||||
|
||||
bool is_command_or_control_pressed() const;
|
||||
|
||||
void set_shift_pressed(bool p_pressed);
|
||||
bool is_shift_pressed() const;
|
||||
|
||||
void set_alt_pressed(bool p_pressed);
|
||||
bool is_alt_pressed() const;
|
||||
|
||||
void set_ctrl_pressed(bool p_pressed);
|
||||
bool is_ctrl_pressed() const;
|
||||
|
||||
void set_meta_pressed(bool p_pressed);
|
||||
bool is_meta_pressed() const;
|
||||
|
||||
void set_modifiers_from_event(const InputEventWithModifiers *event);
|
||||
|
||||
BitField<KeyModifierMask> get_modifiers_mask() const;
|
||||
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventWithModifiers() {}
|
||||
};
|
||||
|
||||
class InputEventKey : public InputEventWithModifiers {
|
||||
GDCLASS(InputEventKey, InputEventWithModifiers);
|
||||
|
||||
Key keycode = Key::NONE; // Key enum, without modifier masks.
|
||||
Key physical_keycode = Key::NONE;
|
||||
Key key_label = Key::NONE;
|
||||
uint32_t unicode = 0; ///unicode
|
||||
KeyLocation location = KeyLocation::UNSPECIFIED;
|
||||
|
||||
bool echo = false; /// true if this is an echo key
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_pressed(bool p_pressed);
|
||||
|
||||
void set_keycode(Key p_keycode);
|
||||
Key get_keycode() const;
|
||||
|
||||
void set_physical_keycode(Key p_keycode);
|
||||
Key get_physical_keycode() const;
|
||||
|
||||
void set_key_label(Key p_key_label);
|
||||
Key get_key_label() const;
|
||||
|
||||
void set_unicode(char32_t p_unicode);
|
||||
char32_t get_unicode() const;
|
||||
|
||||
void set_location(KeyLocation p_key_location);
|
||||
KeyLocation get_location() const;
|
||||
|
||||
void set_echo(bool p_enable);
|
||||
virtual bool is_echo() const override;
|
||||
|
||||
Key get_keycode_with_modifiers() const;
|
||||
Key get_physical_keycode_with_modifiers() const;
|
||||
Key get_key_label_with_modifiers() const;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
|
||||
|
||||
virtual bool is_action_type() const override { return true; }
|
||||
|
||||
virtual String as_text_physical_keycode() const;
|
||||
virtual String as_text_keycode() const;
|
||||
virtual String as_text_key_label() const;
|
||||
virtual String as_text_location() const;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
static Ref<InputEventKey> create_reference(Key p_keycode_with_modifier_masks, bool p_physical = false);
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::KEY; }
|
||||
|
||||
InputEventKey() {}
|
||||
};
|
||||
|
||||
class InputEventMouse : public InputEventWithModifiers {
|
||||
GDCLASS(InputEventMouse, InputEventWithModifiers);
|
||||
|
||||
BitField<MouseButtonMask> button_mask = MouseButtonMask::NONE;
|
||||
|
||||
Vector2 pos;
|
||||
Vector2 global_pos;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_button_mask(BitField<MouseButtonMask> p_mask);
|
||||
BitField<MouseButtonMask> get_button_mask() const;
|
||||
|
||||
void set_position(const Vector2 &p_pos);
|
||||
Vector2 get_position() const;
|
||||
|
||||
void set_global_position(const Vector2 &p_global_pos);
|
||||
Vector2 get_global_position() const;
|
||||
|
||||
InputEventMouse() {}
|
||||
};
|
||||
|
||||
class InputEventMouseButton : public InputEventMouse {
|
||||
GDCLASS(InputEventMouseButton, InputEventMouse);
|
||||
|
||||
float factor = 1;
|
||||
MouseButton button_index = MouseButton::NONE;
|
||||
bool double_click = false; //last even less than double click time
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_factor(float p_factor);
|
||||
float get_factor() const;
|
||||
|
||||
void set_button_index(MouseButton p_index);
|
||||
MouseButton get_button_index() const;
|
||||
|
||||
void set_pressed(bool p_pressed);
|
||||
void set_canceled(bool p_canceled);
|
||||
|
||||
void set_double_click(bool p_double_click);
|
||||
bool is_double_click() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
|
||||
|
||||
virtual bool is_action_type() const override { return true; }
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::MOUSE_BUTTON; }
|
||||
|
||||
InputEventMouseButton() {}
|
||||
};
|
||||
|
||||
class InputEventMouseMotion : public InputEventMouse {
|
||||
GDCLASS(InputEventMouseMotion, InputEventMouse);
|
||||
|
||||
Vector2 tilt;
|
||||
float pressure = 0;
|
||||
Vector2 relative;
|
||||
Vector2 screen_relative;
|
||||
Vector2 velocity;
|
||||
Vector2 screen_velocity;
|
||||
bool pen_inverted = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_tilt(const Vector2 &p_tilt);
|
||||
Vector2 get_tilt() const;
|
||||
|
||||
void set_pressure(float p_pressure);
|
||||
float get_pressure() const;
|
||||
|
||||
void set_pen_inverted(bool p_inverted);
|
||||
bool get_pen_inverted() const;
|
||||
|
||||
void set_relative(const Vector2 &p_relative);
|
||||
Vector2 get_relative() const;
|
||||
|
||||
void set_relative_screen_position(const Vector2 &p_relative);
|
||||
Vector2 get_relative_screen_position() const;
|
||||
|
||||
void set_velocity(const Vector2 &p_velocity);
|
||||
Vector2 get_velocity() const;
|
||||
|
||||
void set_screen_velocity(const Vector2 &p_velocity);
|
||||
Vector2 get_screen_velocity() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
virtual bool accumulate(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::MOUSE_MOTION; }
|
||||
|
||||
InputEventMouseMotion() {}
|
||||
};
|
||||
|
||||
class InputEventJoypadMotion : public InputEvent {
|
||||
GDCLASS(InputEventJoypadMotion, InputEvent);
|
||||
JoyAxis axis = (JoyAxis)0; ///< Joypad axis
|
||||
float axis_value = 0; ///< -1 to 1
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_axis(JoyAxis p_axis);
|
||||
JoyAxis get_axis() const;
|
||||
|
||||
void set_axis_value(float p_value);
|
||||
float get_axis_value() const;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
|
||||
|
||||
virtual bool is_action_type() const override { return true; }
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
static Ref<InputEventJoypadMotion> create_reference(JoyAxis p_axis, float p_value);
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::JOY_MOTION; }
|
||||
|
||||
InputEventJoypadMotion() {}
|
||||
};
|
||||
|
||||
class InputEventJoypadButton : public InputEvent {
|
||||
GDCLASS(InputEventJoypadButton, InputEvent);
|
||||
|
||||
JoyButton button_index = (JoyButton)0;
|
||||
float pressure = 0; //0 to 1
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_button_index(JoyButton p_index);
|
||||
JoyButton get_button_index() const;
|
||||
|
||||
void set_pressed(bool p_pressed);
|
||||
|
||||
void set_pressure(float p_pressure);
|
||||
float get_pressure() const;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
|
||||
|
||||
virtual bool is_action_type() const override { return true; }
|
||||
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
static Ref<InputEventJoypadButton> create_reference(JoyButton p_btn_index);
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::JOY_BUTTON; }
|
||||
|
||||
InputEventJoypadButton() {}
|
||||
};
|
||||
|
||||
class InputEventScreenTouch : public InputEventFromWindow {
|
||||
GDCLASS(InputEventScreenTouch, InputEventFromWindow);
|
||||
int index = 0;
|
||||
Vector2 pos;
|
||||
bool double_tap = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_index(int p_index);
|
||||
int get_index() const;
|
||||
|
||||
void set_position(const Vector2 &p_pos);
|
||||
Vector2 get_position() const;
|
||||
|
||||
void set_pressed(bool p_pressed);
|
||||
void set_canceled(bool p_canceled);
|
||||
|
||||
void set_double_tap(bool p_double_tap);
|
||||
bool is_double_tap() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::SCREEN_TOUCH; }
|
||||
|
||||
InputEventScreenTouch() {}
|
||||
};
|
||||
|
||||
class InputEventScreenDrag : public InputEventFromWindow {
|
||||
GDCLASS(InputEventScreenDrag, InputEventFromWindow);
|
||||
int index = 0;
|
||||
Vector2 pos;
|
||||
Vector2 relative;
|
||||
Vector2 screen_relative;
|
||||
Vector2 velocity;
|
||||
Vector2 screen_velocity;
|
||||
Vector2 tilt;
|
||||
float pressure = 0;
|
||||
bool pen_inverted = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_index(int p_index);
|
||||
int get_index() const;
|
||||
|
||||
void set_tilt(const Vector2 &p_tilt);
|
||||
Vector2 get_tilt() const;
|
||||
|
||||
void set_pressure(float p_pressure);
|
||||
float get_pressure() const;
|
||||
|
||||
void set_pen_inverted(bool p_inverted);
|
||||
bool get_pen_inverted() const;
|
||||
|
||||
void set_position(const Vector2 &p_pos);
|
||||
Vector2 get_position() const;
|
||||
|
||||
void set_relative(const Vector2 &p_relative);
|
||||
Vector2 get_relative() const;
|
||||
|
||||
void set_relative_screen_position(const Vector2 &p_relative);
|
||||
Vector2 get_relative_screen_position() const;
|
||||
|
||||
void set_velocity(const Vector2 &p_velocity);
|
||||
Vector2 get_velocity() const;
|
||||
|
||||
void set_screen_velocity(const Vector2 &p_velocity);
|
||||
Vector2 get_screen_velocity() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
virtual bool accumulate(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::SCREEN_DRAG; }
|
||||
|
||||
InputEventScreenDrag() {}
|
||||
};
|
||||
|
||||
class InputEventAction : public InputEvent {
|
||||
GDCLASS(InputEventAction, InputEvent);
|
||||
|
||||
StringName action;
|
||||
float strength = 1.0f;
|
||||
int event_index = -1;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_action(const StringName &p_action);
|
||||
StringName get_action() const;
|
||||
|
||||
void set_pressed(bool p_pressed);
|
||||
|
||||
void set_strength(float p_strength);
|
||||
float get_strength() const;
|
||||
|
||||
void set_event_index(int p_index);
|
||||
int get_event_index() const;
|
||||
|
||||
virtual bool is_action(const StringName &p_action) const;
|
||||
|
||||
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
|
||||
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
|
||||
|
||||
virtual bool is_action_type() const override { return true; }
|
||||
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::ACTION; }
|
||||
|
||||
InputEventAction() {}
|
||||
};
|
||||
|
||||
class InputEventGesture : public InputEventWithModifiers {
|
||||
GDCLASS(InputEventGesture, InputEventWithModifiers);
|
||||
|
||||
Vector2 pos;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_position(const Vector2 &p_pos);
|
||||
Vector2 get_position() const;
|
||||
};
|
||||
|
||||
class InputEventMagnifyGesture : public InputEventGesture {
|
||||
GDCLASS(InputEventMagnifyGesture, InputEventGesture);
|
||||
real_t factor = 1.0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_factor(real_t p_factor);
|
||||
real_t get_factor() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::MAGNIFY_GESTURE; }
|
||||
|
||||
InputEventMagnifyGesture() {}
|
||||
};
|
||||
|
||||
class InputEventPanGesture : public InputEventGesture {
|
||||
GDCLASS(InputEventPanGesture, InputEventGesture);
|
||||
Vector2 delta;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_delta(const Vector2 &p_delta);
|
||||
Vector2 get_delta() const;
|
||||
|
||||
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::PAN_GESTURE; }
|
||||
|
||||
InputEventPanGesture() {}
|
||||
};
|
||||
|
||||
class InputEventMIDI : public InputEvent {
|
||||
GDCLASS(InputEventMIDI, InputEvent);
|
||||
|
||||
int channel = 0;
|
||||
MIDIMessage message = MIDIMessage::NONE;
|
||||
int pitch = 0;
|
||||
int velocity = 0;
|
||||
int instrument = 0;
|
||||
int pressure = 0;
|
||||
int controller_number = 0;
|
||||
int controller_value = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_channel(const int p_channel);
|
||||
int get_channel() const;
|
||||
|
||||
void set_message(const MIDIMessage p_message);
|
||||
MIDIMessage get_message() const;
|
||||
|
||||
void set_pitch(const int p_pitch);
|
||||
int get_pitch() const;
|
||||
|
||||
void set_velocity(const int p_velocity);
|
||||
int get_velocity() const;
|
||||
|
||||
void set_instrument(const int p_instrument);
|
||||
int get_instrument() const;
|
||||
|
||||
void set_pressure(const int p_pressure);
|
||||
int get_pressure() const;
|
||||
|
||||
void set_controller_number(const int p_controller_number);
|
||||
int get_controller_number() const;
|
||||
|
||||
void set_controller_value(const int p_controller_value);
|
||||
int get_controller_value() const;
|
||||
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::MIDI; }
|
||||
|
||||
InputEventMIDI() {}
|
||||
};
|
||||
|
||||
class InputEventShortcut : public InputEvent {
|
||||
GDCLASS(InputEventShortcut, InputEvent);
|
||||
|
||||
Ref<Shortcut> shortcut;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_shortcut(Ref<Shortcut> p_shortcut);
|
||||
Ref<Shortcut> get_shortcut();
|
||||
|
||||
virtual String as_text() const override;
|
||||
virtual String to_string() override;
|
||||
|
||||
InputEventType get_type() const final override { return InputEventType::SHORTCUT; }
|
||||
|
||||
InputEventShortcut();
|
||||
};
|
||||
474
core/input/input_event_codec.cpp
Normal file
474
core/input/input_event_codec.cpp
Normal file
@@ -0,0 +1,474 @@
|
||||
/**************************************************************************/
|
||||
/* input_event_codec.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 "input_event_codec.h"
|
||||
|
||||
#include "core/input/input.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
enum class BoolShift : uint8_t {
|
||||
SHIFT = 0,
|
||||
CTRL,
|
||||
ALT,
|
||||
META,
|
||||
ECHO,
|
||||
PRESSED,
|
||||
DOUBLE_CLICK,
|
||||
PEN_INVERTED,
|
||||
};
|
||||
|
||||
// cast operator for BoolShift to uint8_t
|
||||
inline uint8_t operator<<(uint8_t a, BoolShift b) {
|
||||
return a << static_cast<uint8_t>(b);
|
||||
}
|
||||
|
||||
uint8_t encode_key_modifier_state(Ref<InputEventWithModifiers> p_event) {
|
||||
uint8_t bools = 0;
|
||||
bools |= (uint8_t)p_event->is_shift_pressed() << BoolShift::SHIFT;
|
||||
bools |= (uint8_t)p_event->is_ctrl_pressed() << BoolShift::CTRL;
|
||||
bools |= (uint8_t)p_event->is_alt_pressed() << BoolShift::ALT;
|
||||
bools |= (uint8_t)p_event->is_meta_pressed() << BoolShift::META;
|
||||
return bools;
|
||||
}
|
||||
|
||||
void decode_key_modifier_state(uint8_t bools, Ref<InputEventWithModifiers> p_event) {
|
||||
p_event->set_shift_pressed(bools & (1 << BoolShift::SHIFT));
|
||||
p_event->set_ctrl_pressed(bools & (1 << BoolShift::CTRL));
|
||||
p_event->set_alt_pressed(bools & (1 << BoolShift::ALT));
|
||||
p_event->set_meta_pressed(bools & (1 << BoolShift::META));
|
||||
}
|
||||
|
||||
int encode_vector2(const Vector2 &p_vector, uint8_t *p_data) {
|
||||
p_data += encode_float(p_vector.x, p_data);
|
||||
encode_float(p_vector.y, p_data);
|
||||
return sizeof(float) * 2;
|
||||
}
|
||||
|
||||
const uint8_t *decode_vector2(Vector2 &r_vector, const uint8_t *p_data) {
|
||||
r_vector.x = decode_float(p_data);
|
||||
p_data += sizeof(float);
|
||||
r_vector.y = decode_float(p_data);
|
||||
p_data += sizeof(float);
|
||||
return p_data;
|
||||
}
|
||||
|
||||
void encode_input_event_key(const Ref<InputEventKey> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(19);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::KEY;
|
||||
data++;
|
||||
uint8_t bools = encode_key_modifier_state(p_event);
|
||||
bools |= (uint8_t)p_event->is_echo() << BoolShift::ECHO;
|
||||
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
|
||||
*data = bools;
|
||||
data++;
|
||||
data += encode_uint32((uint32_t)p_event->get_keycode(), data);
|
||||
data += encode_uint32((uint32_t)p_event->get_physical_keycode(), data);
|
||||
data += encode_uint32((uint32_t)p_event->get_key_label(), data);
|
||||
data += encode_uint32(p_event->get_unicode(), data);
|
||||
*data = (uint8_t)p_event->get_location();
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
Error decode_input_event_key(const PackedByteArray &p_data, Ref<InputEventKey> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::KEY);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
decode_key_modifier_state(bools, r_event);
|
||||
r_event->set_echo(bools & (1 << BoolShift::ECHO));
|
||||
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
|
||||
|
||||
Key keycode = (Key)decode_uint32(data);
|
||||
data += sizeof(uint32_t);
|
||||
Key physical_keycode = (Key)decode_uint32(data);
|
||||
data += sizeof(uint32_t);
|
||||
Key key_label = (Key)decode_uint32(data);
|
||||
data += sizeof(uint32_t);
|
||||
char32_t unicode = (char32_t)decode_uint32(data);
|
||||
data += sizeof(uint32_t);
|
||||
KeyLocation location = (KeyLocation)*data;
|
||||
|
||||
r_event->set_keycode(keycode);
|
||||
r_event->set_physical_keycode(physical_keycode);
|
||||
r_event->set_key_label(key_label);
|
||||
r_event->set_unicode(unicode);
|
||||
r_event->set_location(location);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void encode_input_event_mouse_button(const Ref<InputEventMouseButton> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(12);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::MOUSE_BUTTON;
|
||||
data++;
|
||||
|
||||
uint8_t bools = encode_key_modifier_state(p_event);
|
||||
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
|
||||
bools |= (uint8_t)p_event->is_double_click() << BoolShift::DOUBLE_CLICK;
|
||||
*data = bools;
|
||||
data++;
|
||||
|
||||
*data = (uint8_t)p_event->get_button_index();
|
||||
data++;
|
||||
|
||||
// Rather than use encode_variant, we explicitly encode the Vector2,
|
||||
// so decoding is easier. Specifically, we don't have to perform additional error
|
||||
// checking for decoding the variant and then checking the variant type.
|
||||
data += encode_vector2(p_event->get_position(), data);
|
||||
*data = (uint8_t)p_event->get_button_mask();
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
Error decode_input_event_mouse_button(const PackedByteArray &p_data, Ref<InputEventMouseButton> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MOUSE_BUTTON);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
decode_key_modifier_state(bools, r_event);
|
||||
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
|
||||
r_event->set_double_click(bools & (1 << BoolShift::DOUBLE_CLICK));
|
||||
|
||||
r_event->set_button_index((MouseButton)*data);
|
||||
data++;
|
||||
|
||||
Vector2 pos;
|
||||
data = decode_vector2(pos, data);
|
||||
r_event->set_position(pos);
|
||||
r_event->set_global_position(pos); // these are the same
|
||||
BitField<MouseButtonMask> button_mask = (MouseButtonMask)*data;
|
||||
r_event->set_button_mask(button_mask);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void encode_input_event_mouse_motion(const Ref<InputEventMouseMotion> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(31);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::MOUSE_MOTION;
|
||||
data++;
|
||||
|
||||
uint8_t bools = encode_key_modifier_state(p_event);
|
||||
bools |= (uint8_t)p_event->get_pen_inverted() << BoolShift::PEN_INVERTED;
|
||||
*data = bools;
|
||||
data++;
|
||||
|
||||
// Rather than use encode_variant, we explicitly encode the Vector2,
|
||||
// so decoding is easier. Specifically, we don't have to perform additional error
|
||||
// checking for decoding the variant and then checking the variant type.
|
||||
data += encode_vector2(p_event->get_position(), data);
|
||||
data += encode_float(p_event->get_pressure(), data);
|
||||
data += encode_vector2(p_event->get_tilt(), data);
|
||||
data += encode_vector2(p_event->get_relative(), data);
|
||||
*data = (uint8_t)p_event->get_button_mask();
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
void decode_input_event_mouse_motion(const PackedByteArray &p_data, Ref<InputEventMouseMotion> &r_event) {
|
||||
Input *input = Input::get_singleton();
|
||||
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MOUSE_MOTION);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
decode_key_modifier_state(bools, r_event);
|
||||
r_event->set_pen_inverted(bools & (1 << BoolShift::PEN_INVERTED));
|
||||
|
||||
{
|
||||
Vector2 pos;
|
||||
data = decode_vector2(pos, data);
|
||||
r_event->set_position(pos);
|
||||
r_event->set_global_position(pos); // these are the same
|
||||
}
|
||||
r_event->set_pressure(decode_float(data));
|
||||
data += sizeof(float);
|
||||
{
|
||||
Vector2 tilt;
|
||||
data = decode_vector2(tilt, data);
|
||||
r_event->set_tilt(tilt);
|
||||
}
|
||||
r_event->set_velocity(input->get_last_mouse_velocity());
|
||||
r_event->set_screen_velocity(input->get_last_mouse_screen_velocity());
|
||||
{
|
||||
Vector2 relative;
|
||||
data = decode_vector2(relative, data);
|
||||
r_event->set_relative(relative);
|
||||
r_event->set_relative_screen_position(relative);
|
||||
}
|
||||
BitField<MouseButtonMask> button_mask = (MouseButtonMask)*data;
|
||||
r_event->set_button_mask(button_mask);
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
|
||||
}
|
||||
|
||||
void encode_input_event_joypad_button(const Ref<InputEventJoypadButton> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(11);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::JOY_BUTTON;
|
||||
data++;
|
||||
|
||||
uint8_t bools = 0;
|
||||
bools |= (uint8_t)p_event->is_pressed() << BoolShift::PRESSED;
|
||||
*data = bools;
|
||||
data++;
|
||||
|
||||
data += encode_uint64(p_event->get_device(), data);
|
||||
*data = (uint8_t)p_event->get_button_index();
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
void decode_input_event_joypad_button(const PackedByteArray &p_data, Ref<InputEventJoypadButton> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::JOY_BUTTON);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
r_event->set_pressed(bools & (1 << BoolShift::PRESSED));
|
||||
r_event->set_device(decode_uint64(data));
|
||||
data += sizeof(uint64_t);
|
||||
r_event->set_button_index((JoyButton)*data);
|
||||
data++;
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
|
||||
}
|
||||
|
||||
void encode_input_event_joypad_motion(const Ref<InputEventJoypadMotion> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(14);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::JOY_MOTION;
|
||||
data++;
|
||||
|
||||
data += encode_uint64(p_event->get_device(), data);
|
||||
*data = (uint8_t)p_event->get_axis();
|
||||
data++;
|
||||
data += encode_float(p_event->get_axis_value(), data);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
void decode_input_event_joypad_motion(const PackedByteArray &p_data, Ref<InputEventJoypadMotion> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::JOY_MOTION);
|
||||
data++; // Skip event type.
|
||||
|
||||
r_event->set_device(decode_uint64(data));
|
||||
data += sizeof(uint64_t);
|
||||
r_event->set_axis((JoyAxis)*data);
|
||||
data++;
|
||||
r_event->set_axis_value(decode_float(data));
|
||||
data += sizeof(float);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
|
||||
}
|
||||
|
||||
void encode_input_event_gesture_pan(const Ref<InputEventPanGesture> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(18);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::PAN_GESTURE;
|
||||
data++;
|
||||
|
||||
uint8_t bools = encode_key_modifier_state(p_event);
|
||||
*data = bools;
|
||||
data++;
|
||||
data += encode_vector2(p_event->get_position(), data);
|
||||
data += encode_vector2(p_event->get_delta(), data);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
void decode_input_event_gesture_pan(const PackedByteArray &p_data, Ref<InputEventPanGesture> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::PAN_GESTURE);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
decode_key_modifier_state(bools, r_event);
|
||||
|
||||
Vector2 pos;
|
||||
data = decode_vector2(pos, data);
|
||||
r_event->set_position(pos);
|
||||
Vector2 delta;
|
||||
data = decode_vector2(delta, data);
|
||||
r_event->set_delta(delta);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
|
||||
}
|
||||
|
||||
void encode_input_event_gesture_magnify(const Ref<InputEventMagnifyGesture> &p_event, PackedByteArray &r_data) {
|
||||
r_data.resize(14);
|
||||
|
||||
uint8_t *data = r_data.ptrw();
|
||||
*data = (uint8_t)InputEventType::MAGNIFY_GESTURE;
|
||||
data++;
|
||||
|
||||
uint8_t bools = encode_key_modifier_state(p_event);
|
||||
*data = bools;
|
||||
data++;
|
||||
data += encode_vector2(p_event->get_position(), data);
|
||||
data += encode_float(p_event->get_factor(), data);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
|
||||
}
|
||||
|
||||
void decode_input_event_gesture_magnify(const PackedByteArray &p_data, Ref<InputEventMagnifyGesture> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
DEV_ASSERT(static_cast<InputEventType>(*data) == InputEventType::MAGNIFY_GESTURE);
|
||||
data++; // Skip event type.
|
||||
|
||||
uint8_t bools = *data;
|
||||
data++;
|
||||
decode_key_modifier_state(bools, r_event);
|
||||
|
||||
Vector2 pos;
|
||||
data = decode_vector2(pos, data);
|
||||
r_event->set_position(pos);
|
||||
r_event->set_factor(decode_float(data));
|
||||
data += sizeof(float);
|
||||
|
||||
// Assert we had enough space.
|
||||
DEV_ASSERT(data - p_data.ptr() >= p_data.size());
|
||||
}
|
||||
|
||||
bool encode_input_event(const Ref<InputEvent> &p_event, PackedByteArray &r_data) {
|
||||
switch (p_event->get_type()) {
|
||||
case InputEventType::KEY:
|
||||
encode_input_event_key(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::MOUSE_BUTTON:
|
||||
encode_input_event_mouse_button(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::MOUSE_MOTION:
|
||||
encode_input_event_mouse_motion(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::JOY_MOTION:
|
||||
encode_input_event_joypad_motion(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::JOY_BUTTON:
|
||||
encode_input_event_joypad_button(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::MAGNIFY_GESTURE:
|
||||
encode_input_event_gesture_magnify(p_event, r_data);
|
||||
break;
|
||||
case InputEventType::PAN_GESTURE:
|
||||
encode_input_event_gesture_pan(p_event, r_data);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void decode_input_event(const PackedByteArray &p_data, Ref<InputEvent> &r_event) {
|
||||
const uint8_t *data = p_data.ptr();
|
||||
|
||||
switch (static_cast<InputEventType>(*data)) {
|
||||
case InputEventType::KEY: {
|
||||
Ref<InputEventKey> event;
|
||||
event.instantiate();
|
||||
decode_input_event_key(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::MOUSE_BUTTON: {
|
||||
Ref<InputEventMouseButton> event;
|
||||
event.instantiate();
|
||||
decode_input_event_mouse_button(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::MOUSE_MOTION: {
|
||||
Ref<InputEventMouseMotion> event;
|
||||
event.instantiate();
|
||||
decode_input_event_mouse_motion(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::JOY_BUTTON: {
|
||||
Ref<InputEventJoypadButton> event;
|
||||
event.instantiate();
|
||||
decode_input_event_joypad_button(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::JOY_MOTION: {
|
||||
Ref<InputEventJoypadMotion> event;
|
||||
event.instantiate();
|
||||
decode_input_event_joypad_motion(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::PAN_GESTURE: {
|
||||
Ref<InputEventPanGesture> event;
|
||||
event.instantiate();
|
||||
decode_input_event_gesture_pan(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
case InputEventType::MAGNIFY_GESTURE: {
|
||||
Ref<InputEventMagnifyGesture> event;
|
||||
event.instantiate();
|
||||
decode_input_event_gesture_magnify(p_data, event);
|
||||
r_event = event;
|
||||
} break;
|
||||
default: {
|
||||
WARN_PRINT(vformat("Unknown event type %d.", static_cast<int>(*data)));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
49
core/input/input_event_codec.h
Normal file
49
core/input/input_event_codec.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/**************************************************************************/
|
||||
/* input_event_codec.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
|
||||
/**
|
||||
* Encodes the input event as a byte array.
|
||||
*
|
||||
* Returns `true` if the event was successfully encoded, `false` otherwise.
|
||||
*/
|
||||
bool encode_input_event(const Ref<InputEvent> &p_event, PackedByteArray &r_data);
|
||||
void decode_input_event(const PackedByteArray &p_data, Ref<InputEvent> &r_event);
|
||||
|
||||
void encode_input_event_key(const Ref<InputEventKey> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_mouse_button(const Ref<InputEventMouseButton> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_mouse_motion(const Ref<InputEventMouseMotion> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_joypad_button(const Ref<InputEventJoypadButton> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_joypad_motion(const Ref<InputEventJoypadMotion> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_gesture_pan(const Ref<InputEventPanGesture> &p_event, PackedByteArray &r_data);
|
||||
void encode_input_event_gesture_magnify(const Ref<InputEventMagnifyGesture> &p_event, PackedByteArray &r_data);
|
||||
41
core/input/input_map.compat.inc
Normal file
41
core/input/input_map.compat.inc
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* input_map.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
|
||||
|
||||
void InputMap::_add_action_bind_compat_97281(const StringName &p_action, float p_deadzone) {
|
||||
add_action(p_action, p_deadzone);
|
||||
}
|
||||
|
||||
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
|
||||
905
core/input/input_map.cpp
Normal file
905
core/input/input_map.cpp
Normal file
@@ -0,0 +1,905 @@
|
||||
/**************************************************************************/
|
||||
/* input_map.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 "input_map.h"
|
||||
#include "input_map.compat.inc"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/input/input.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
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(DEFAULT_DEADZONE));
|
||||
ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_action_description", "action"), &InputMap::get_action_description);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone);
|
||||
ClassDB::bind_method(D_METHOD("action_get_deadzone", "action"), &InputMap::action_get_deadzone);
|
||||
ClassDB::bind_method(D_METHOD("action_add_event", "action", "event"), &InputMap::action_add_event);
|
||||
ClassDB::bind_method(D_METHOD("action_has_event", "action", "event"), &InputMap::action_has_event);
|
||||
ClassDB::bind_method(D_METHOD("action_erase_event", "action", "event"), &InputMap::action_erase_event);
|
||||
ClassDB::bind_method(D_METHOD("action_erase_events", "action"), &InputMap::action_erase_events);
|
||||
ClassDB::bind_method(D_METHOD("action_get_events", "action"), &InputMap::_action_get_events);
|
||||
ClassDB::bind_method(D_METHOD("event_is_action", "event", "action", "exact_match"), &InputMap::event_is_action, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("load_from_project_settings"), &InputMap::load_from_project_settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an nonexistent action error message with a suggestion of the closest
|
||||
* matching action name (if possible).
|
||||
*/
|
||||
String InputMap::suggest_actions(const StringName &p_action) const {
|
||||
StringName closest_action;
|
||||
float closest_similarity = 0.0;
|
||||
|
||||
// Find the most action with the most similar name.
|
||||
for (const KeyValue<StringName, Action> &kv : input_map) {
|
||||
const float similarity = String(kv.key).similarity(p_action);
|
||||
|
||||
if (similarity > closest_similarity) {
|
||||
closest_action = kv.key;
|
||||
closest_similarity = similarity;
|
||||
}
|
||||
}
|
||||
|
||||
String error_message = vformat("The InputMap action \"%s\" doesn't exist.", p_action);
|
||||
|
||||
if (closest_similarity >= 0.4) {
|
||||
// Only include a suggestion in the error message if it's similar enough.
|
||||
error_message += vformat(" Did you mean \"%s\"?", closest_action);
|
||||
}
|
||||
return error_message;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void InputMap::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
|
||||
const String pf = p_function;
|
||||
bool first_argument_is_action = false;
|
||||
if (p_idx == 0) {
|
||||
first_argument_is_action = (pf == "has_action" || pf == "erase_action" ||
|
||||
pf == "action_set_deadzone" || pf == "action_get_deadzone" ||
|
||||
pf == "action_has_event" || pf == "action_add_event" || pf == "action_get_events" ||
|
||||
pf == "action_erase_event" || pf == "action_erase_events");
|
||||
}
|
||||
if (first_argument_is_action || (p_idx == 1 && pf == "event_is_action")) {
|
||||
// Cannot rely on `get_actions()`, otherwise the actions would be in the context of the Editor (no user-defined actions).
|
||||
List<PropertyInfo> pinfo;
|
||||
ProjectSettings::get_singleton()->get_property_list(&pinfo);
|
||||
|
||||
for (const PropertyInfo &pi : pinfo) {
|
||||
if (!pi.name.begins_with("input/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = pi.name.substr(pi.name.find_char('/') + 1);
|
||||
r_options->push_back(name.quote());
|
||||
}
|
||||
}
|
||||
|
||||
Object::get_argument_options(p_function, p_idx, r_options);
|
||||
}
|
||||
#endif
|
||||
|
||||
void InputMap::add_action(const StringName &p_action, float p_deadzone) {
|
||||
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;
|
||||
input_map[p_action].deadzone = p_deadzone;
|
||||
last_id++;
|
||||
}
|
||||
|
||||
void InputMap::erase_action(const StringName &p_action) {
|
||||
ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
|
||||
|
||||
input_map.erase(p_action);
|
||||
}
|
||||
|
||||
TypedArray<StringName> InputMap::get_actions() {
|
||||
TypedArray<StringName> ret;
|
||||
|
||||
ret.resize(input_map.size());
|
||||
|
||||
uint32_t i = 0;
|
||||
for (const KeyValue<StringName, Action> &kv : input_map) {
|
||||
ret[i] = kv.key;
|
||||
i++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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_null(), nullptr);
|
||||
|
||||
int i = 0;
|
||||
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
|
||||
int device = E->get()->get_device();
|
||||
if (device == ALL_DEVICES || device == p_event->get_device()) {
|
||||
if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) {
|
||||
if (r_event_index) {
|
||||
*r_event_index = i;
|
||||
}
|
||||
return E;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool InputMap::has_action(const StringName &p_action) const {
|
||||
return input_map.has(p_action);
|
||||
}
|
||||
|
||||
String InputMap::get_action_description(const StringName &p_action) const {
|
||||
ERR_FAIL_COND_V_MSG(!input_map.has(p_action), String(), suggest_actions(p_action));
|
||||
|
||||
String ret;
|
||||
const List<Ref<InputEvent>> &inputs = input_map[p_action].inputs;
|
||||
for (Ref<InputEventKey> iek : inputs) {
|
||||
if (iek.is_valid()) {
|
||||
if (!ret.is_empty()) {
|
||||
ret += RTR(" or ");
|
||||
}
|
||||
ret += iek->as_text();
|
||||
}
|
||||
}
|
||||
if (ret.is_empty()) {
|
||||
ret = RTR("Action has no bound inputs");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
float InputMap::action_get_deadzone(const StringName &p_action) {
|
||||
ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, suggest_actions(p_action));
|
||||
|
||||
return input_map[p_action].deadzone;
|
||||
}
|
||||
|
||||
void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) {
|
||||
ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
|
||||
|
||||
input_map[p_action].deadzone = p_deadzone;
|
||||
}
|
||||
|
||||
void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND_MSG(p_event.is_null(), "It's not a reference to a valid InputEvent object.");
|
||||
ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
|
||||
if (_find_event(input_map[p_action], p_event, true)) {
|
||||
return; // Already added.
|
||||
}
|
||||
|
||||
input_map[p_action].inputs.push_back(p_event);
|
||||
}
|
||||
|
||||
bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND_V_MSG(!input_map.has(p_action), false, suggest_actions(p_action));
|
||||
return (_find_event(input_map[p_action], p_event, true) != nullptr);
|
||||
}
|
||||
|
||||
void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
|
||||
|
||||
List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event, true);
|
||||
if (E) {
|
||||
input_map[p_action].inputs.erase(E);
|
||||
|
||||
if (Input::get_singleton()->is_action_pressed(p_action)) {
|
||||
Input::get_singleton()->action_release(p_action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputMap::action_erase_events(const StringName &p_action) {
|
||||
ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
|
||||
|
||||
input_map[p_action].inputs.clear();
|
||||
}
|
||||
|
||||
TypedArray<InputEvent> InputMap::_action_get_events(const StringName &p_action) {
|
||||
TypedArray<InputEvent> ret;
|
||||
const List<Ref<InputEvent>> *al = action_get_events(p_action);
|
||||
if (al) {
|
||||
for (const List<Ref<InputEvent>>::Element *E = al->front(); E; E = E->next()) {
|
||||
ret.push_back(E->get());
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const List<Ref<InputEvent>> *InputMap::action_get_events(const StringName &p_action) {
|
||||
HashMap<StringName, Action>::Iterator E = input_map.find(p_action);
|
||||
if (!E) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &E->value.inputs;
|
||||
}
|
||||
|
||||
bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match) const {
|
||||
return event_get_action_status(p_event, p_action, p_exact_match);
|
||||
}
|
||||
|
||||
int InputMap::event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match) const {
|
||||
int index = -1;
|
||||
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 {
|
||||
HashMap<StringName, Action>::Iterator E = input_map.find(p_action);
|
||||
ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action));
|
||||
|
||||
Ref<InputEventAction> input_event_action = p_event;
|
||||
if (input_event_action.is_valid()) {
|
||||
const bool pressed = input_event_action->is_pressed();
|
||||
if (r_pressed != nullptr) {
|
||||
*r_pressed = pressed;
|
||||
}
|
||||
const float strength = pressed ? input_event_action->get_strength() : 0.0f;
|
||||
if (r_strength != nullptr) {
|
||||
*r_strength = strength;
|
||||
}
|
||||
if (r_raw_strength != nullptr) {
|
||||
*r_raw_strength = strength;
|
||||
}
|
||||
if (r_event_index) {
|
||||
if (input_event_action->get_event_index() >= 0) {
|
||||
*r_event_index = input_event_action->get_event_index();
|
||||
} else {
|
||||
*r_event_index = E->value.inputs.size();
|
||||
}
|
||||
}
|
||||
return input_event_action->get_action() == p_action;
|
||||
}
|
||||
|
||||
List<Ref<InputEvent>>::Element *event = _find_event(E->value, p_event, p_exact_match, r_pressed, r_strength, r_raw_strength, r_event_index);
|
||||
return event != nullptr;
|
||||
}
|
||||
|
||||
const HashMap<StringName, InputMap::Action> &InputMap::get_action_map() const {
|
||||
return input_map;
|
||||
}
|
||||
|
||||
void InputMap::load_from_project_settings() {
|
||||
input_map.clear();
|
||||
|
||||
List<PropertyInfo> pinfo;
|
||||
ProjectSettings::get_singleton()->get_property_list(&pinfo);
|
||||
|
||||
for (const PropertyInfo &pi : pinfo) {
|
||||
if (!pi.name.begins_with("input/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = pi.name.substr(pi.name.find_char('/') + 1);
|
||||
|
||||
Dictionary action = GLOBAL_GET(pi.name);
|
||||
float deadzone = action.has("deadzone") ? (float)action["deadzone"] : DEFAULT_DEADZONE;
|
||||
Array events = action["events"];
|
||||
|
||||
add_action(name, deadzone);
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
Ref<InputEvent> event = events[i];
|
||||
if (event.is_null()) {
|
||||
continue;
|
||||
}
|
||||
action_add_event(name, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct _BuiltinActionDisplayName {
|
||||
const char *name;
|
||||
const char *display_name;
|
||||
};
|
||||
|
||||
static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
|
||||
/* clang-format off */
|
||||
{ "ui_accept", TTRC("Accept") },
|
||||
{ "ui_select", TTRC("Select") },
|
||||
{ "ui_cancel", TTRC("Cancel") },
|
||||
{ "ui_focus_next", TTRC("Focus Next") },
|
||||
{ "ui_focus_prev", TTRC("Focus Prev") },
|
||||
{ "ui_left", TTRC("Left") },
|
||||
{ "ui_right", TTRC("Right") },
|
||||
{ "ui_up", TTRC("Up") },
|
||||
{ "ui_down", TTRC("Down") },
|
||||
{ "ui_page_up", TTRC("Page Up") },
|
||||
{ "ui_page_down", TTRC("Page Down") },
|
||||
{ "ui_home", TTRC("Home") },
|
||||
{ "ui_end", TTRC("End") },
|
||||
{ "ui_cut", TTRC("Cut") },
|
||||
{ "ui_copy", TTRC("Copy") },
|
||||
{ "ui_paste", TTRC("Paste") },
|
||||
{ "ui_focus_mode", TTRC("Toggle Tab Focus Mode") },
|
||||
{ "ui_undo", TTRC("Undo") },
|
||||
{ "ui_redo", TTRC("Redo") },
|
||||
{ "ui_text_completion_query", TTRC("Completion Query") },
|
||||
{ "ui_text_newline", TTRC("New Line") },
|
||||
{ "ui_text_newline_blank", TTRC("New Blank Line") },
|
||||
{ "ui_text_newline_above", TTRC("New Line Above") },
|
||||
{ "ui_text_indent", TTRC("Indent") },
|
||||
{ "ui_text_dedent", TTRC("Dedent") },
|
||||
{ "ui_text_backspace", TTRC("Backspace") },
|
||||
{ "ui_text_backspace_word", TTRC("Backspace Word") },
|
||||
{ "ui_text_backspace_word.macos", TTRC("Backspace Word") },
|
||||
{ "ui_text_backspace_all_to_left", TTRC("Backspace all to Left") },
|
||||
{ "ui_text_backspace_all_to_left.macos", TTRC("Backspace all to Left") },
|
||||
{ "ui_text_delete", TTRC("Delete") },
|
||||
{ "ui_text_delete_word", TTRC("Delete Word") },
|
||||
{ "ui_text_delete_word.macos", TTRC("Delete Word") },
|
||||
{ "ui_text_delete_all_to_right", TTRC("Delete all to Right") },
|
||||
{ "ui_text_delete_all_to_right.macos", TTRC("Delete all to Right") },
|
||||
{ "ui_text_caret_left", TTRC("Caret Left") },
|
||||
{ "ui_text_caret_word_left", TTRC("Caret Word Left") },
|
||||
{ "ui_text_caret_word_left.macos", TTRC("Caret Word Left") },
|
||||
{ "ui_text_caret_right", TTRC("Caret Right") },
|
||||
{ "ui_text_caret_word_right", TTRC("Caret Word Right") },
|
||||
{ "ui_text_caret_word_right.macos", TTRC("Caret Word Right") },
|
||||
{ "ui_text_caret_up", TTRC("Caret Up") },
|
||||
{ "ui_text_caret_down", TTRC("Caret Down") },
|
||||
{ "ui_text_caret_line_start", TTRC("Caret Line Start") },
|
||||
{ "ui_text_caret_line_start.macos", TTRC("Caret Line Start") },
|
||||
{ "ui_text_caret_line_end", TTRC("Caret Line End") },
|
||||
{ "ui_text_caret_line_end.macos", TTRC("Caret Line End") },
|
||||
{ "ui_text_caret_page_up", TTRC("Caret Page Up") },
|
||||
{ "ui_text_caret_page_down", TTRC("Caret Page Down") },
|
||||
{ "ui_text_caret_document_start", TTRC("Caret Document Start") },
|
||||
{ "ui_text_caret_document_start.macos", TTRC("Caret Document Start") },
|
||||
{ "ui_text_caret_document_end", TTRC("Caret Document End") },
|
||||
{ "ui_text_caret_document_end.macos", TTRC("Caret Document End") },
|
||||
{ "ui_text_caret_add_below", TTRC("Caret Add Below") },
|
||||
{ "ui_text_caret_add_below.macos", TTRC("Caret Add Below") },
|
||||
{ "ui_text_caret_add_above", TTRC("Caret Add Above") },
|
||||
{ "ui_text_caret_add_above.macos", TTRC("Caret Add Above") },
|
||||
{ "ui_text_scroll_up", TTRC("Scroll Up") },
|
||||
{ "ui_text_scroll_up.macos", TTRC("Scroll Up") },
|
||||
{ "ui_text_scroll_down", TTRC("Scroll Down") },
|
||||
{ "ui_text_scroll_down.macos", TTRC("Scroll Down") },
|
||||
{ "ui_text_select_all", TTRC("Select All") },
|
||||
{ "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") },
|
||||
{ "ui_text_add_selection_for_next_occurrence", TTRC("Add Selection for Next Occurrence") },
|
||||
{ "ui_text_skip_selection_for_next_occurrence", TTRC("Skip Selection for Next Occurrence") },
|
||||
{ "ui_text_clear_carets_and_selection", TTRC("Clear Carets and Selection") },
|
||||
{ "ui_text_toggle_insert_mode", TTRC("Toggle Insert Mode") },
|
||||
{ "ui_text_submit", TTRC("Submit Text") },
|
||||
{ "ui_graph_duplicate", TTRC("Duplicate Nodes") },
|
||||
{ "ui_graph_delete", TTRC("Delete Nodes") },
|
||||
{ "ui_graph_follow_left", TTRC("Follow Input Port Connection") },
|
||||
{ "ui_graph_follow_right", TTRC("Follow Output Port Connection") },
|
||||
{ "ui_filedialog_up_one_level", TTRC("Go Up One Level") },
|
||||
{ "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") },
|
||||
{ "ui_colorpicker_delete_preset", TTRC("ColorPicker: Delete Preset") },
|
||||
{ "ui_accessibility_drag_and_drop", TTRC("Accessibility: Keyboard Drag and Drop") },
|
||||
{ "", ""}
|
||||
/* clang-format on */
|
||||
};
|
||||
|
||||
String InputMap::get_builtin_display_name(const String &p_name) const {
|
||||
constexpr int len = std::size(_builtin_action_display_names);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (_builtin_action_display_names[i].name == p_name) {
|
||||
return _builtin_action_display_names[i].display_name;
|
||||
}
|
||||
}
|
||||
|
||||
return p_name;
|
||||
}
|
||||
|
||||
const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
|
||||
// Return cache if it has already been built.
|
||||
if (default_builtin_cache.size()) {
|
||||
return default_builtin_cache;
|
||||
}
|
||||
|
||||
List<Ref<InputEvent>> inputs;
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::SPACE));
|
||||
default_builtin_cache.insert("ui_accept", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::Y));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::SPACE));
|
||||
default_builtin_cache.insert("ui_select", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ESCAPE));
|
||||
default_builtin_cache.insert("ui_cancel", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::TAB));
|
||||
default_builtin_cache.insert("ui_focus_next", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::TAB | KeyModifierMask::SHIFT));
|
||||
default_builtin_cache.insert("ui_focus_prev", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT));
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0));
|
||||
default_builtin_cache.insert("ui_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT));
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0));
|
||||
default_builtin_cache.insert("ui_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP));
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0));
|
||||
default_builtin_cache.insert("ui_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN));
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0));
|
||||
default_builtin_cache.insert("ui_down", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::PAGEUP));
|
||||
default_builtin_cache.insert("ui_page_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::PAGEDOWN));
|
||||
default_builtin_cache.insert("ui_page_down", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::HOME));
|
||||
default_builtin_cache.insert("ui_home", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::END));
|
||||
default_builtin_cache.insert("ui_end", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
default_builtin_cache.insert("ui_accessibility_drag_and_drop", inputs);
|
||||
|
||||
// ///// UI basic Shortcuts /////
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::X | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE | KeyModifierMask::SHIFT));
|
||||
default_builtin_cache.insert("ui_cut", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::C | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_copy", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::M | KeyModifierMask::CTRL));
|
||||
default_builtin_cache.insert("ui_focus_mode", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::V | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::INSERT | KeyModifierMask::SHIFT));
|
||||
default_builtin_cache.insert("ui_paste", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::Z | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_undo", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::Z | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::Y | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_redo", inputs);
|
||||
|
||||
// ///// UI Text Input Shortcuts /////
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::SPACE | KeyModifierMask::CTRL));
|
||||
default_builtin_cache.insert("ui_text_completion_query", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
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
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
|
||||
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);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ENTER | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_newline_above", inputs);
|
||||
|
||||
// Indentation
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::TAB));
|
||||
default_builtin_cache.insert("ui_text_indent", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::TAB | KeyModifierMask::SHIFT));
|
||||
default_builtin_cache.insert("ui_text_dedent", inputs);
|
||||
|
||||
// Text Backspace and Delete
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::SHIFT));
|
||||
default_builtin_cache.insert("ui_text_backspace", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_backspace_word", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_backspace_word.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
default_builtin_cache.insert("ui_text_backspace_all_to_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_backspace_all_to_left.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE));
|
||||
default_builtin_cache.insert("ui_text_delete", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_delete_word", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_delete_word.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
default_builtin_cache.insert("ui_text_delete_all_to_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_delete_all_to_right.macos", inputs);
|
||||
|
||||
// Text Caret Movement Left/Right
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT));
|
||||
default_builtin_cache.insert("ui_text_caret_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_word_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_caret_word_left.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT));
|
||||
default_builtin_cache.insert("ui_text_caret_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_word_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_caret_word_right.macos", inputs);
|
||||
|
||||
// Text Caret Movement Up/Down
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP));
|
||||
default_builtin_cache.insert("ui_text_caret_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN));
|
||||
default_builtin_cache.insert("ui_text_caret_down", inputs);
|
||||
|
||||
// Text Caret Movement Line Start/End
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::HOME));
|
||||
default_builtin_cache.insert("ui_text_caret_line_start", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::HOME));
|
||||
default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::END));
|
||||
default_builtin_cache.insert("ui_text_caret_line_end", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::END));
|
||||
default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs);
|
||||
|
||||
// Text Caret Movement Page Up/Down
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::PAGEUP));
|
||||
default_builtin_cache.insert("ui_text_caret_page_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::PAGEDOWN));
|
||||
default_builtin_cache.insert("ui_text_caret_page_down", inputs);
|
||||
|
||||
// Text Caret Movement Document Start/End
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_document_start", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_document_end", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs);
|
||||
|
||||
// Text Caret Addition Below/Above
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_below", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::L | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_below.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_above", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::O | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_caret_add_above.macos", inputs);
|
||||
|
||||
// Text Scrolling
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_scroll_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_scroll_up.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_scroll_down", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_scroll_down.macos", inputs);
|
||||
|
||||
// Text Misc
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_select_all", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::G | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_select_word_under_caret", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::G | KeyModifierMask::CTRL | KeyModifierMask::META));
|
||||
default_builtin_cache.insert("ui_text_select_word_under_caret.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_text_add_selection_for_next_occurrence", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_text_skip_selection_for_next_occurrence", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ESCAPE));
|
||||
default_builtin_cache.insert("ui_text_clear_carets_and_selection", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::INSERT));
|
||||
default_builtin_cache.insert("ui_text_toggle_insert_mode", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::MENU));
|
||||
default_builtin_cache.insert("ui_menu", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
|
||||
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>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_graph_duplicate", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE));
|
||||
default_builtin_cache.insert("ui_graph_delete", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_graph_follow_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_graph_follow_left.macos", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_graph_follow_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::ALT));
|
||||
default_builtin_cache.insert("ui_graph_follow_right.macos", inputs);
|
||||
|
||||
// ///// UI File Dialog Shortcuts /////
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::BACKSPACE));
|
||||
default_builtin_cache.insert("ui_filedialog_up_one_level", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::F5));
|
||||
default_builtin_cache.insert("ui_filedialog_refresh", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::H));
|
||||
default_builtin_cache.insert("ui_filedialog_show_hidden", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::QUOTELEFT | KeyModifierMask::CMD_OR_CTRL));
|
||||
default_builtin_cache.insert("ui_swap_input_direction", inputs);
|
||||
|
||||
// ///// UI ColorPicker Shortcuts /////
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::X));
|
||||
inputs.push_back(InputEventKey::create_reference(Key::KEY_DELETE));
|
||||
default_builtin_cache.insert("ui_colorpicker_delete_preset", inputs);
|
||||
|
||||
return default_builtin_cache;
|
||||
}
|
||||
|
||||
const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins_with_feature_overrides_applied() {
|
||||
if (default_builtin_with_overrides_cache.size() > 0) {
|
||||
return default_builtin_with_overrides_cache;
|
||||
}
|
||||
|
||||
const HashMap<String, List<Ref<InputEvent>>> &builtins = get_builtins();
|
||||
|
||||
// Get a list of all built in inputs which are valid overrides for the OS
|
||||
// Key = builtin name (e.g. ui_accept)
|
||||
// Value = override/feature names (e.g. macos, if it was defined as "ui_accept.macos" and the platform supports that feature)
|
||||
HashMap<String, Vector<String>> builtins_with_overrides;
|
||||
for (const KeyValue<String, List<Ref<InputEvent>>> &E : builtins) {
|
||||
String fullname = E.key;
|
||||
|
||||
Vector<String> split = fullname.split(".");
|
||||
const String &name = split[0];
|
||||
String override_for = split.size() > 1 ? split[1] : String();
|
||||
|
||||
if (!override_for.is_empty() && OS::get_singleton()->has_feature(override_for)) {
|
||||
builtins_with_overrides[name].push_back(override_for);
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<String, List<Ref<InputEvent>>> &E : builtins) {
|
||||
String fullname = E.key;
|
||||
|
||||
Vector<String> split = fullname.split(".");
|
||||
const String &name = split[0];
|
||||
String override_for = split.size() > 1 ? split[1] : String();
|
||||
|
||||
if (builtins_with_overrides.has(name) && override_for.is_empty()) {
|
||||
// Builtin has an override but this particular one is not an override, so skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!override_for.is_empty() && !OS::get_singleton()->has_feature(override_for)) {
|
||||
// OS does not support this override - skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
default_builtin_with_overrides_cache.insert(name, E.value);
|
||||
}
|
||||
|
||||
return default_builtin_with_overrides_cache;
|
||||
}
|
||||
|
||||
void InputMap::load_default() {
|
||||
HashMap<String, List<Ref<InputEvent>>> builtins = get_builtins_with_feature_overrides_applied();
|
||||
|
||||
for (const KeyValue<String, List<Ref<InputEvent>>> &E : builtins) {
|
||||
String name = E.key;
|
||||
|
||||
add_action(name);
|
||||
|
||||
const List<Ref<InputEvent>> &inputs = E.value;
|
||||
for (const List<Ref<InputEvent>>::Element *I = inputs.front(); I; I = I->next()) {
|
||||
Ref<InputEventKey> iek = I->get();
|
||||
|
||||
// For the editor, only add keyboard actions.
|
||||
if (iek.is_valid()) {
|
||||
action_add_event(name, I->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InputMap::InputMap() {
|
||||
ERR_FAIL_COND_MSG(singleton, "Singleton in InputMap already exists.");
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
InputMap::~InputMap() {
|
||||
singleton = nullptr;
|
||||
}
|
||||
117
core/input/input_map.h
Normal file
117
core/input/input_map.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/**************************************************************************/
|
||||
/* input_map.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
class InputMap : public Object {
|
||||
GDCLASS(InputMap, Object);
|
||||
|
||||
public:
|
||||
/**
|
||||
* A special value used to signify that a given Action can be triggered by any device
|
||||
*/
|
||||
static constexpr int ALL_DEVICES = -1;
|
||||
|
||||
struct Action {
|
||||
int id;
|
||||
float deadzone;
|
||||
List<Ref<InputEvent>> inputs;
|
||||
};
|
||||
|
||||
static constexpr float DEFAULT_DEADZONE = 0.2f;
|
||||
// Keep bigger deadzone for toggle actions (default `ui_*` actions, axis `pressed`) (GH-103360).
|
||||
static constexpr float DEFAULT_TOGGLE_DEADZONE = 0.5f;
|
||||
|
||||
private:
|
||||
static inline InputMap *singleton = nullptr;
|
||||
|
||||
mutable HashMap<StringName, Action> input_map;
|
||||
HashMap<String, List<Ref<InputEvent>>> default_builtin_cache;
|
||||
HashMap<String, List<Ref<InputEvent>>> default_builtin_with_overrides_cache;
|
||||
|
||||
List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const;
|
||||
|
||||
TypedArray<InputEvent> _action_get_events(const StringName &p_action);
|
||||
|
||||
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;
|
||||
TypedArray<StringName> get_actions();
|
||||
void add_action(const StringName &p_action, float p_deadzone = DEFAULT_DEADZONE);
|
||||
void erase_action(const StringName &p_action);
|
||||
|
||||
String get_action_description(const StringName &p_action) const;
|
||||
|
||||
float action_get_deadzone(const StringName &p_action);
|
||||
void action_set_deadzone(const StringName &p_action, float p_deadzone);
|
||||
void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event);
|
||||
bool action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event);
|
||||
void action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event);
|
||||
void action_erase_events(const StringName &p_action);
|
||||
|
||||
const List<Ref<InputEvent>> *action_get_events(const StringName &p_action);
|
||||
bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const;
|
||||
int event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const;
|
||||
bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const;
|
||||
|
||||
const HashMap<StringName, Action> &get_action_map() const;
|
||||
void load_from_project_settings();
|
||||
void load_default();
|
||||
|
||||
String suggest_actions(const StringName &p_action) const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
String get_builtin_display_name(const String &p_name) const;
|
||||
// Use an Ordered Map so insertion order is preserved. We want the elements to be 'grouped' somewhat.
|
||||
const HashMap<String, List<Ref<InputEvent>>> &get_builtins();
|
||||
const HashMap<String, List<Ref<InputEvent>>> &get_builtins_with_feature_overrides_applied();
|
||||
|
||||
InputMap();
|
||||
~InputMap();
|
||||
};
|
||||
131
core/input/shortcut.cpp
Normal file
131
core/input/shortcut.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/**************************************************************************/
|
||||
/* shortcut.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 "shortcut.h"
|
||||
|
||||
void Shortcut::set_events(const Array &p_events) {
|
||||
for (int i = 0; i < p_events.size(); i++) {
|
||||
Ref<InputEventShortcut> ies = p_events[i];
|
||||
ERR_FAIL_COND_MSG(ies.is_valid(), "Cannot set a shortcut event to an instance of InputEventShortcut.");
|
||||
}
|
||||
|
||||
events = p_events;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
void Shortcut::set_events_list(const List<Ref<InputEvent>> *p_events) {
|
||||
events.clear();
|
||||
|
||||
for (const Ref<InputEvent> &ie : *p_events) {
|
||||
events.push_back(ie);
|
||||
}
|
||||
}
|
||||
|
||||
Array Shortcut::get_events() const {
|
||||
return events;
|
||||
}
|
||||
|
||||
bool Shortcut::matches_event(const Ref<InputEvent> &p_event) const {
|
||||
Ref<InputEventShortcut> ies = p_event;
|
||||
if (ies.is_valid()) {
|
||||
if (ies->get_shortcut().ptr() == this) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
Ref<InputEvent> ie = events[i];
|
||||
bool valid = ie.is_valid() && ie->is_match(p_event);
|
||||
|
||||
// Stop on first valid event - don't need to check further.
|
||||
if (valid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String Shortcut::get_as_text() const {
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
Ref<InputEvent> ie = events[i];
|
||||
// Return first shortcut which is valid
|
||||
if (ie.is_valid()) {
|
||||
return ie->as_text();
|
||||
}
|
||||
}
|
||||
|
||||
return "None";
|
||||
}
|
||||
|
||||
bool Shortcut::has_valid_event() const {
|
||||
// Tests if there is ANY input event which is valid.
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
Ref<InputEvent> ie = events[i];
|
||||
if (ie.is_valid()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Shortcut::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_events", "events"), &Shortcut::set_events);
|
||||
ClassDB::bind_method(D_METHOD("get_events"), &Shortcut::get_events);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_valid_event"), &Shortcut::has_valid_event);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("matches_event", "event"), &Shortcut::matches_event);
|
||||
ClassDB::bind_method(D_METHOD("get_as_text"), &Shortcut::get_as_text);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "events", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("InputEvent")), "set_events", "get_events");
|
||||
}
|
||||
|
||||
bool Shortcut::is_event_array_equal(const Array &p_event_array1, const Array &p_event_array2) {
|
||||
if (p_event_array1.size() != p_event_array2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_same = true;
|
||||
for (int i = 0; i < p_event_array1.size(); i++) {
|
||||
Ref<InputEvent> ie_1 = p_event_array1[i];
|
||||
Ref<InputEvent> ie_2 = p_event_array2[i];
|
||||
|
||||
is_same = ie_1->is_match(ie_2);
|
||||
|
||||
// Break on the first that doesn't match - don't need to check further.
|
||||
if (!is_same) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return is_same;
|
||||
}
|
||||
56
core/input/shortcut.h
Normal file
56
core/input/shortcut.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**************************************************************************/
|
||||
/* shortcut.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/io/resource.h"
|
||||
|
||||
class Shortcut : public Resource {
|
||||
GDCLASS(Shortcut, Resource);
|
||||
|
||||
Array events;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_events(const Array &p_events);
|
||||
Array get_events() const;
|
||||
|
||||
void set_events_list(const List<Ref<InputEvent>> *p_events);
|
||||
|
||||
bool matches_event(const Ref<InputEvent> &p_event) const;
|
||||
bool has_valid_event() const;
|
||||
|
||||
String get_as_text() const;
|
||||
|
||||
static bool is_event_array_equal(const Array &p_event_array1, const Array &p_event_array2);
|
||||
};
|
||||
6
core/io/SCsub
Normal file
6
core/io/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.core_sources, "*.cpp")
|
||||
371
core/io/compression.cpp
Normal file
371
core/io/compression.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
/**************************************************************************/
|
||||
/* compression.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 "compression.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/zip_io.h"
|
||||
|
||||
#include "thirdparty/misc/fastlz.h"
|
||||
|
||||
#include <zstd.h>
|
||||
|
||||
#ifdef BROTLI_ENABLED
|
||||
#include <brotli/decode.h>
|
||||
#endif
|
||||
|
||||
// Caches for zstd.
|
||||
static BinaryMutex mutex;
|
||||
static ZSTD_DCtx *current_zstd_d_ctx = nullptr;
|
||||
static bool current_zstd_long_distance_matching;
|
||||
static int current_zstd_window_log_size;
|
||||
|
||||
int64_t Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
if (p_src_size < 16) {
|
||||
uint8_t src[16];
|
||||
memset(&src[p_src_size], 0, 16 - p_src_size);
|
||||
memcpy(src, p_src, p_src_size);
|
||||
return fastlz_compress(src, 16, p_dst);
|
||||
} else {
|
||||
return fastlz_compress(p_src, p_src_size, p_dst);
|
||||
}
|
||||
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
int level = p_mode == MODE_DEFLATE ? zlib_level : gzip_level;
|
||||
int err = deflateInit2(&strm, level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
|
||||
if (err != Z_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
strm.avail_in = p_src_size;
|
||||
int aout = deflateBound(&strm, p_src_size);
|
||||
strm.avail_out = aout;
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.next_out = p_dst;
|
||||
deflate(&strm, Z_FINISH);
|
||||
aout = aout - strm.avail_out;
|
||||
deflateEnd(&strm);
|
||||
return aout;
|
||||
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
ZSTD_CCtx *cctx = ZSTD_createCCtx();
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, zstd_level);
|
||||
if (zstd_long_distance_matching) {
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_enableLongDistanceMatching, 1);
|
||||
ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog, zstd_window_log_size);
|
||||
}
|
||||
const int64_t max_dst_size = get_max_compressed_buffer_size(p_src_size, MODE_ZSTD);
|
||||
const size_t ret = ZSTD_compressCCtx(cctx, p_dst, max_dst_size, p_src, p_src_size, zstd_level);
|
||||
ZSTD_freeCCtx(cctx);
|
||||
return (int64_t)ret;
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
int64_t Compression::get_max_compressed_buffer_size(int64_t p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
int ss = p_src_size + p_src_size * 6 / 100;
|
||||
if (ss < 66) {
|
||||
ss = 66;
|
||||
}
|
||||
return ss;
|
||||
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
ERR_FAIL_COND_V_MSG(p_src_size > INT32_MAX, -1, "Cannot compress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
int err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
|
||||
if (err != Z_OK) {
|
||||
return -1;
|
||||
}
|
||||
int aout = deflateBound(&strm, p_src_size);
|
||||
deflateEnd(&strm);
|
||||
return aout;
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
return ZSTD_compressBound(p_src_size);
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
int64_t Compression::decompress(uint8_t *p_dst, int64_t p_dst_max_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
|
||||
switch (p_mode) {
|
||||
case MODE_BROTLI: {
|
||||
#ifdef BROTLI_ENABLED
|
||||
size_t ret_size = p_dst_max_size;
|
||||
BrotliDecoderResult res = BrotliDecoderDecompress(p_src_size, p_src, &ret_size, p_dst);
|
||||
ERR_FAIL_COND_V(res != BROTLI_DECODER_RESULT_SUCCESS, -1);
|
||||
return ret_size;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(-1, "Godot was compiled without brotli support.");
|
||||
#endif
|
||||
} break;
|
||||
case MODE_FASTLZ: {
|
||||
ERR_FAIL_COND_V_MSG(p_dst_max_size > INT32_MAX, -1, "Cannot decompress a FastLZ/LZ77 file 2 GiB or larger. LZ77 supports larger files, but FastLZ's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
int ret_size = 0;
|
||||
|
||||
if (p_dst_max_size < 16) {
|
||||
uint8_t dst[16];
|
||||
fastlz_decompress(p_src, p_src_size, dst, 16);
|
||||
memcpy(p_dst, dst, p_dst_max_size);
|
||||
ret_size = p_dst_max_size;
|
||||
} else {
|
||||
ret_size = fastlz_decompress(p_src, p_src_size, p_dst, p_dst_max_size);
|
||||
}
|
||||
return ret_size;
|
||||
} break;
|
||||
case MODE_DEFLATE:
|
||||
case MODE_GZIP: {
|
||||
ERR_FAIL_COND_V_MSG(p_dst_max_size > INT32_MAX, -1, "Cannot decompress a Deflate or GZip file 2 GiB or larger. Deflate and GZip are both limited to 4 GiB, and ZLib's implementation uses C++ `int` so is limited to 2 GiB. Consider using Zstd instead.");
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = zipio_alloc;
|
||||
strm.zfree = zipio_free;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
int err = inflateInit2(&strm, window_bits);
|
||||
ERR_FAIL_COND_V(err != Z_OK, -1);
|
||||
|
||||
strm.avail_in = p_src_size;
|
||||
strm.avail_out = p_dst_max_size;
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.next_out = p_dst;
|
||||
|
||||
err = inflate(&strm, Z_FINISH);
|
||||
int total = strm.total_out;
|
||||
inflateEnd(&strm);
|
||||
ERR_FAIL_COND_V(err != Z_STREAM_END, -1);
|
||||
return total;
|
||||
} break;
|
||||
case MODE_ZSTD: {
|
||||
MutexLock lock(mutex);
|
||||
|
||||
if (!current_zstd_d_ctx || current_zstd_long_distance_matching != zstd_long_distance_matching || current_zstd_window_log_size != zstd_window_log_size) {
|
||||
if (current_zstd_d_ctx) {
|
||||
ZSTD_freeDCtx(current_zstd_d_ctx);
|
||||
}
|
||||
|
||||
current_zstd_d_ctx = ZSTD_createDCtx();
|
||||
if (zstd_long_distance_matching) {
|
||||
ZSTD_DCtx_setParameter(current_zstd_d_ctx, ZSTD_d_windowLogMax, zstd_window_log_size);
|
||||
}
|
||||
current_zstd_long_distance_matching = zstd_long_distance_matching;
|
||||
current_zstd_window_log_size = zstd_window_log_size;
|
||||
}
|
||||
|
||||
size_t ret = ZSTD_decompressDCtx(current_zstd_d_ctx, p_dst, p_dst_max_size, p_src, p_src_size);
|
||||
return (int64_t)ret;
|
||||
} break;
|
||||
}
|
||||
|
||||
ERR_FAIL_V(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
This will handle both Gzip and Deflate streams. It will automatically allocate the output buffer into the provided p_dst_vect Vector.
|
||||
This is required for compressed data whose final uncompressed size is unknown, as is the case for HTTP response bodies.
|
||||
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
|
||||
*/
|
||||
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int64_t p_max_dst_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode) {
|
||||
uint8_t *dst = nullptr;
|
||||
int out_mark = 0;
|
||||
|
||||
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
|
||||
|
||||
if (p_mode == MODE_BROTLI) {
|
||||
#ifdef BROTLI_ENABLED
|
||||
BrotliDecoderResult ret;
|
||||
BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
|
||||
ERR_FAIL_NULL_V(state, Z_DATA_ERROR);
|
||||
|
||||
// Setup the stream inputs.
|
||||
const uint8_t *next_in = p_src;
|
||||
size_t avail_in = p_src_size;
|
||||
uint8_t *next_out = nullptr;
|
||||
size_t avail_out = 0;
|
||||
size_t total_out = 0;
|
||||
|
||||
// Ensure the destination buffer is empty.
|
||||
p_dst_vect->clear();
|
||||
|
||||
// Decompress until stream ends or end of file.
|
||||
do {
|
||||
// Add another chunk size to the output buffer.
|
||||
// This forces a copy of the whole buffer.
|
||||
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
|
||||
// Get pointer to the actual output buffer.
|
||||
dst = p_dst_vect->ptrw();
|
||||
|
||||
// Set the stream to the new output stream.
|
||||
// Since it was copied, we need to reset the stream to the new buffer.
|
||||
next_out = &(dst[out_mark]);
|
||||
avail_out += gzip_chunk;
|
||||
|
||||
ret = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, &total_out);
|
||||
if (ret == BROTLI_DECODER_RESULT_ERROR) {
|
||||
WARN_PRINT(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)));
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
p_dst_vect->clear();
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
out_mark += gzip_chunk - avail_out;
|
||||
|
||||
// Enforce max output size.
|
||||
if (p_max_dst_size > -1 && total_out > (uint64_t)p_max_dst_size) {
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
p_dst_vect->clear();
|
||||
return Z_BUF_ERROR;
|
||||
}
|
||||
} while (ret != BROTLI_DECODER_RESULT_SUCCESS);
|
||||
|
||||
// If all done successfully, resize the output if it's larger than the actual output.
|
||||
if ((unsigned long)p_dst_vect->size() > total_out) {
|
||||
p_dst_vect->resize(total_out);
|
||||
}
|
||||
|
||||
// Clean up and return.
|
||||
BrotliDecoderDestroyInstance(state);
|
||||
return Z_OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(Z_ERRNO, "Godot was compiled without brotli support.");
|
||||
#endif
|
||||
} else {
|
||||
// This function only supports GZip and Deflate.
|
||||
ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
|
||||
|
||||
int ret;
|
||||
z_stream strm;
|
||||
int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
|
||||
|
||||
// Initialize the stream.
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
|
||||
int err = inflateInit2(&strm, window_bits);
|
||||
ERR_FAIL_COND_V(err != Z_OK, -1);
|
||||
|
||||
// Setup the stream inputs.
|
||||
strm.next_in = (Bytef *)p_src;
|
||||
strm.avail_in = p_src_size;
|
||||
|
||||
// Ensure the destination buffer is empty.
|
||||
p_dst_vect->clear();
|
||||
|
||||
// Decompress until deflate stream ends or end of file.
|
||||
do {
|
||||
// Add another chunk size to the output buffer.
|
||||
// This forces a copy of the whole buffer.
|
||||
p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
|
||||
// Get pointer to the actual output buffer.
|
||||
dst = p_dst_vect->ptrw();
|
||||
|
||||
// Set the stream to the new output stream.
|
||||
// Since it was copied, we need to reset the stream to the new buffer.
|
||||
strm.next_out = &(dst[out_mark]);
|
||||
strm.avail_out = gzip_chunk;
|
||||
|
||||
// Run inflate() on input until output buffer is full and needs to be resized or input runs out.
|
||||
do {
|
||||
ret = inflate(&strm, Z_SYNC_FLUSH);
|
||||
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR;
|
||||
[[fallthrough]];
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
case Z_STREAM_ERROR:
|
||||
case Z_BUF_ERROR:
|
||||
if (strm.msg) {
|
||||
WARN_PRINT(strm.msg);
|
||||
}
|
||||
(void)inflateEnd(&strm);
|
||||
p_dst_vect->clear();
|
||||
return ret;
|
||||
}
|
||||
} while (strm.avail_out > 0 && strm.avail_in > 0);
|
||||
|
||||
out_mark += gzip_chunk;
|
||||
|
||||
// Enforce max output size.
|
||||
if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
|
||||
(void)inflateEnd(&strm);
|
||||
p_dst_vect->clear();
|
||||
return Z_BUF_ERROR;
|
||||
}
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
// If all done successfully, resize the output if it's larger than the actual output.
|
||||
if ((unsigned long)p_dst_vect->size() > strm.total_out) {
|
||||
p_dst_vect->resize(strm.total_out);
|
||||
}
|
||||
|
||||
// Clean up and return.
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_OK;
|
||||
}
|
||||
}
|
||||
59
core/io/compression.h
Normal file
59
core/io/compression.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**************************************************************************/
|
||||
/* compression.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
class Compression {
|
||||
public:
|
||||
static inline int zlib_level = Z_DEFAULT_COMPRESSION;
|
||||
static inline int gzip_level = Z_DEFAULT_COMPRESSION;
|
||||
static inline int zstd_level = 3;
|
||||
static inline bool zstd_long_distance_matching = false;
|
||||
static inline int zstd_window_log_size = 27; // ZSTD_WINDOWLOG_LIMIT_DEFAULT
|
||||
static inline int gzip_chunk = 16384;
|
||||
|
||||
enum Mode : int32_t {
|
||||
MODE_FASTLZ,
|
||||
MODE_DEFLATE,
|
||||
MODE_ZSTD,
|
||||
MODE_GZIP,
|
||||
MODE_BROTLI
|
||||
};
|
||||
|
||||
static int64_t compress(uint8_t *p_dst, const uint8_t *p_src, int64_t p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int64_t get_max_compressed_buffer_size(int64_t p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int64_t decompress(uint8_t *p_dst, int64_t p_dst_max_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode = MODE_ZSTD);
|
||||
static int decompress_dynamic(Vector<uint8_t> *p_dst_vect, int64_t p_max_dst_size, const uint8_t *p_src, int64_t p_src_size, Mode p_mode);
|
||||
};
|
||||
338
core/io/config_file.cpp
Normal file
338
core/io/config_file.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
/**************************************************************************/
|
||||
/* config_file.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 "config_file.h"
|
||||
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/string/string_builder.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
void ConfigFile::set_value(const String &p_section, const String &p_key, const Variant &p_value) {
|
||||
if (p_value.get_type() == Variant::NIL) { // Erase key.
|
||||
if (!values.has(p_section)) {
|
||||
return;
|
||||
}
|
||||
|
||||
values[p_section].erase(p_key);
|
||||
if (values[p_section].is_empty()) {
|
||||
values.erase(p_section);
|
||||
}
|
||||
} else {
|
||||
if (!values.has(p_section)) {
|
||||
// Insert section-less keys at the beginning.
|
||||
values.insert(p_section, HashMap<String, Variant>(), p_section.is_empty());
|
||||
}
|
||||
|
||||
values[p_section][p_key] = p_value;
|
||||
}
|
||||
}
|
||||
|
||||
Variant ConfigFile::get_value(const String &p_section, const String &p_key, const Variant &p_default) const {
|
||||
if (!values.has(p_section) || !values[p_section].has(p_key)) {
|
||||
ERR_FAIL_COND_V_MSG(p_default.get_type() == Variant::NIL, Variant(),
|
||||
vformat("Couldn't find the given section \"%s\" and key \"%s\", and no default was given.", p_section, p_key));
|
||||
return p_default;
|
||||
}
|
||||
|
||||
return values[p_section][p_key];
|
||||
}
|
||||
|
||||
bool ConfigFile::has_section(const String &p_section) const {
|
||||
return values.has(p_section);
|
||||
}
|
||||
|
||||
bool ConfigFile::has_section_key(const String &p_section, const String &p_key) const {
|
||||
if (!values.has(p_section)) {
|
||||
return false;
|
||||
}
|
||||
return values[p_section].has(p_key);
|
||||
}
|
||||
|
||||
Vector<String> ConfigFile::get_sections() const {
|
||||
Vector<String> sections;
|
||||
sections.resize(values.size());
|
||||
|
||||
int i = 0;
|
||||
String *sections_write = sections.ptrw();
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
sections_write[i++] = E.key;
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
Vector<String> ConfigFile::get_section_keys(const String &p_section) const {
|
||||
Vector<String> keys;
|
||||
ERR_FAIL_COND_V_MSG(!values.has(p_section), keys, vformat("Cannot get keys from nonexistent section \"%s\".", p_section));
|
||||
|
||||
const HashMap<String, Variant> &keys_map = values[p_section];
|
||||
keys.resize(keys_map.size());
|
||||
|
||||
int i = 0;
|
||||
String *keys_write = keys.ptrw();
|
||||
for (const KeyValue<String, Variant> &E : keys_map) {
|
||||
keys_write[i++] = E.key;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
void ConfigFile::erase_section(const String &p_section) {
|
||||
ERR_FAIL_COND_MSG(!values.has(p_section), vformat("Cannot erase nonexistent section \"%s\".", p_section));
|
||||
values.erase(p_section);
|
||||
}
|
||||
|
||||
void ConfigFile::erase_section_key(const String &p_section, const String &p_key) {
|
||||
ERR_FAIL_COND_MSG(!values.has(p_section), vformat("Cannot erase key \"%s\" from nonexistent section \"%s\".", p_key, p_section));
|
||||
ERR_FAIL_COND_MSG(!values[p_section].has(p_key), vformat("Cannot erase nonexistent key \"%s\" from section \"%s\".", p_key, p_section));
|
||||
|
||||
values[p_section].erase(p_key);
|
||||
if (values[p_section].is_empty()) {
|
||||
values.erase(p_section);
|
||||
}
|
||||
}
|
||||
|
||||
String ConfigFile::encode_to_text() const {
|
||||
StringBuilder sb;
|
||||
bool first = true;
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append("\n");
|
||||
}
|
||||
if (!E.key.is_empty()) {
|
||||
sb.append("[" + E.key + "]\n\n");
|
||||
}
|
||||
|
||||
for (const KeyValue<String, Variant> &F : E.value) {
|
||||
String vstr;
|
||||
VariantWriter::write_to_string(F.value, vstr);
|
||||
sb.append(F.key.property_name_encode() + "=" + vstr + "\n");
|
||||
}
|
||||
}
|
||||
return sb.as_string();
|
||||
}
|
||||
|
||||
Error ConfigFile::save(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_save(file);
|
||||
}
|
||||
|
||||
Error ConfigFile::save_encrypted(const String &p_path, const Vector<uint8_t> &p_key) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse(f, p_key, FileAccessEncrypted::MODE_WRITE_AES256);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return _internal_save(fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::save_encrypted_pass(const String &p_path, const String &p_pass) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse_password(f, p_pass, FileAccessEncrypted::MODE_WRITE_AES256);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_save(fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::_internal_save(Ref<FileAccess> file) {
|
||||
bool first = true;
|
||||
for (const KeyValue<String, HashMap<String, Variant>> &E : values) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
file->store_string("\n");
|
||||
}
|
||||
if (!E.key.is_empty()) {
|
||||
file->store_string("[" + E.key.replace("]", "\\]") + "]\n\n");
|
||||
}
|
||||
|
||||
for (const KeyValue<String, Variant> &F : E.value) {
|
||||
String vstr;
|
||||
VariantWriter::write_to_string(F.value, vstr);
|
||||
file->store_string(F.key.property_name_encode() + "=" + vstr + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ConfigFile::load(const String &p_path) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (f.is_null()) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_load(p_path, f);
|
||||
}
|
||||
|
||||
Error ConfigFile::load_encrypted(const String &p_path, const Vector<uint8_t> &p_key) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse(f, p_key, FileAccessEncrypted::MODE_READ);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
return _internal_load(p_path, fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::load_encrypted_pass(const String &p_path, const String &p_pass) {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
err = fae->open_and_parse_password(f, p_pass, FileAccessEncrypted::MODE_READ);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return _internal_load(p_path, fae);
|
||||
}
|
||||
|
||||
Error ConfigFile::_internal_load(const String &p_path, Ref<FileAccess> f) {
|
||||
VariantParser::StreamFile stream;
|
||||
stream.f = f;
|
||||
|
||||
Error err = _parse(p_path, &stream);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
Error ConfigFile::parse(const String &p_data) {
|
||||
VariantParser::StreamString stream;
|
||||
stream.s = p_data;
|
||||
return _parse("<string>", &stream);
|
||||
}
|
||||
|
||||
Error ConfigFile::_parse(const String &p_path, VariantParser::Stream *p_stream) {
|
||||
String assign;
|
||||
Variant value;
|
||||
VariantParser::Tag next_tag;
|
||||
|
||||
int lines = 0;
|
||||
String error_text;
|
||||
|
||||
String section;
|
||||
|
||||
while (true) {
|
||||
assign = Variant();
|
||||
next_tag.fields.clear();
|
||||
next_tag.name = String();
|
||||
|
||||
Error err = VariantParser::parse_tag_assign_eof(p_stream, lines, error_text, next_tag, assign, value, nullptr, true);
|
||||
if (err == ERR_FILE_EOF) {
|
||||
return OK;
|
||||
} else if (err != OK) {
|
||||
ERR_PRINT(vformat("ConfigFile parse error at %s:%d: %s.", p_path, lines, error_text));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!assign.is_empty()) {
|
||||
set_value(section, assign, value);
|
||||
} else if (!next_tag.name.is_empty()) {
|
||||
section = next_tag.name.replace("\\]", "]");
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ConfigFile::clear() {
|
||||
values.clear();
|
||||
}
|
||||
|
||||
void ConfigFile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_value", "section", "key", "value"), &ConfigFile::set_value);
|
||||
ClassDB::bind_method(D_METHOD("get_value", "section", "key", "default"), &ConfigFile::get_value, DEFVAL(Variant()));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_section", "section"), &ConfigFile::has_section);
|
||||
ClassDB::bind_method(D_METHOD("has_section_key", "section", "key"), &ConfigFile::has_section_key);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_sections"), &ConfigFile::get_sections);
|
||||
ClassDB::bind_method(D_METHOD("get_section_keys", "section"), &ConfigFile::get_section_keys);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("erase_section", "section"), &ConfigFile::erase_section);
|
||||
ClassDB::bind_method(D_METHOD("erase_section_key", "section", "key"), &ConfigFile::erase_section_key);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("load", "path"), &ConfigFile::load);
|
||||
ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse);
|
||||
ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("encode_to_text"), &ConfigFile::encode_to_text);
|
||||
|
||||
BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted);
|
||||
ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("save_encrypted", "path", "key"), &ConfigFile::save_encrypted);
|
||||
ClassDB::bind_method(D_METHOD("save_encrypted_pass", "path", "password"), &ConfigFile::save_encrypted_pass);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("clear"), &ConfigFile::clear);
|
||||
}
|
||||
77
core/io/config_file.h
Normal file
77
core/io/config_file.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************/
|
||||
/* config_file.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
|
||||
class ConfigFile : public RefCounted {
|
||||
GDCLASS(ConfigFile, RefCounted);
|
||||
|
||||
HashMap<String, HashMap<String, Variant>> values;
|
||||
|
||||
Error _internal_load(const String &p_path, Ref<FileAccess> f);
|
||||
Error _internal_save(Ref<FileAccess> file);
|
||||
|
||||
Error _parse(const String &p_path, VariantParser::Stream *p_stream);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_value(const String &p_section, const String &p_key, const Variant &p_value);
|
||||
Variant get_value(const String &p_section, const String &p_key, const Variant &p_default = Variant()) const;
|
||||
|
||||
bool has_section(const String &p_section) const;
|
||||
bool has_section_key(const String &p_section, const String &p_key) const;
|
||||
|
||||
Vector<String> get_sections() const;
|
||||
Vector<String> get_section_keys(const String &p_section) const;
|
||||
|
||||
void erase_section(const String &p_section);
|
||||
void erase_section_key(const String &p_section, const String &p_key);
|
||||
|
||||
Error save(const String &p_path);
|
||||
Error load(const String &p_path);
|
||||
Error parse(const String &p_data);
|
||||
|
||||
String encode_to_text() const; // used by exporter
|
||||
|
||||
void clear();
|
||||
|
||||
Error load_encrypted(const String &p_path, const Vector<uint8_t> &p_key);
|
||||
Error load_encrypted_pass(const String &p_path, const String &p_pass);
|
||||
|
||||
Error save_encrypted(const String &p_path, const Vector<uint8_t> &p_key);
|
||||
Error save_encrypted_pass(const String &p_path, const String &p_pass);
|
||||
};
|
||||
689
core/io/dir_access.cpp
Normal file
689
core/io/dir_access.cpp
Normal file
@@ -0,0 +1,689 @@
|
||||
/**************************************************************************/
|
||||
/* dir_access.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 "dir_access.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/time.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
String DirAccess::_get_root_path() const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES:
|
||||
return ProjectSettings::get_singleton()->get_resource_path();
|
||||
case ACCESS_USERDATA:
|
||||
return OS::get_singleton()->get_user_data_dir();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String DirAccess::_get_root_string() const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES:
|
||||
return "res://";
|
||||
case ACCESS_USERDATA:
|
||||
return "user://";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
int DirAccess::get_current_drive() {
|
||||
String path = get_current_dir().to_lower();
|
||||
for (int i = 0; i < get_drive_count(); i++) {
|
||||
String d = get_drive(i).to_lower();
|
||||
if (path.begins_with(d)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirAccess::drives_are_shortcuts() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static Error _erase_recursive(DirAccess *da) {
|
||||
List<String> dirs;
|
||||
List<String> files;
|
||||
|
||||
da->list_dir_begin();
|
||||
String n = da->get_next();
|
||||
while (!n.is_empty()) {
|
||||
if (n != "." && n != "..") {
|
||||
if (da->current_is_dir() && !da->is_link(n)) {
|
||||
dirs.push_back(n);
|
||||
} else {
|
||||
files.push_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
n = da->get_next();
|
||||
}
|
||||
|
||||
da->list_dir_end();
|
||||
|
||||
for (const String &E : dirs) {
|
||||
Error err = da->change_dir(E);
|
||||
if (err == OK) {
|
||||
err = _erase_recursive(da);
|
||||
if (err) {
|
||||
da->change_dir("..");
|
||||
return err;
|
||||
}
|
||||
err = da->change_dir("..");
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
err = da->remove(da->get_current_dir().path_join(E));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
for (const String &E : files) {
|
||||
Error err = da->remove(da->get_current_dir().path_join(E));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error DirAccess::erase_contents_recursive() {
|
||||
return _erase_recursive(this);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_recursive(const String &p_dir) {
|
||||
if (p_dir.length() < 1) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
String full_dir;
|
||||
|
||||
if (p_dir.is_relative_path()) {
|
||||
//append current
|
||||
full_dir = get_current_dir().path_join(p_dir);
|
||||
|
||||
} else {
|
||||
full_dir = p_dir;
|
||||
}
|
||||
|
||||
full_dir = full_dir.replace_char('\\', '/');
|
||||
|
||||
String base;
|
||||
|
||||
if (full_dir.begins_with("res://")) {
|
||||
base = "res://";
|
||||
} else if (full_dir.begins_with("user://")) {
|
||||
base = "user://";
|
||||
} else if (full_dir.is_network_share_path()) {
|
||||
int pos = full_dir.find_char('/', 2);
|
||||
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
|
||||
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("/")) {
|
||||
base = "/";
|
||||
} else if (full_dir.contains(":/")) {
|
||||
base = full_dir.substr(0, full_dir.find(":/") + 2);
|
||||
} else {
|
||||
ERR_FAIL_V(ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
full_dir = full_dir.replace_first(base, "").simplify_path();
|
||||
|
||||
Vector<String> subdirs = full_dir.split("/");
|
||||
|
||||
String curpath = base;
|
||||
for (int i = 0; i < subdirs.size(); i++) {
|
||||
curpath = curpath.path_join(subdirs[i]);
|
||||
Error err = make_dir(curpath);
|
||||
if (err != OK && err != ERR_ALREADY_EXISTS) {
|
||||
ERR_FAIL_V_MSG(err, vformat("Could not create directory: '%s'.", curpath));
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
DirAccess::AccessType DirAccess::get_access_type() const {
|
||||
return _access_type;
|
||||
}
|
||||
|
||||
String DirAccess::fix_path(const String &p_path) const {
|
||||
switch (_access_type) {
|
||||
case ACCESS_RESOURCES: {
|
||||
if (ProjectSettings::get_singleton()) {
|
||||
if (p_path.begins_with("res://")) {
|
||||
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
if (!resource_path.is_empty()) {
|
||||
return p_path.replace_first("res:/", resource_path);
|
||||
}
|
||||
return p_path.replace_first("res://", "");
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_USERDATA: {
|
||||
if (p_path.begins_with("user://")) {
|
||||
String data_dir = OS::get_singleton()->get_user_data_dir();
|
||||
if (!data_dir.is_empty()) {
|
||||
return p_path.replace_first("user:/", data_dir);
|
||||
}
|
||||
return p_path.replace_first("user://", "");
|
||||
}
|
||||
|
||||
} break;
|
||||
case ACCESS_FILESYSTEM: {
|
||||
return p_path;
|
||||
} break;
|
||||
case ACCESS_MAX:
|
||||
break; // Can't happen, but silences warning
|
||||
}
|
||||
|
||||
return p_path;
|
||||
}
|
||||
|
||||
DirAccess::CreateFunc DirAccess::create_func[ACCESS_MAX] = { nullptr, nullptr, nullptr };
|
||||
|
||||
Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
|
||||
Ref<DirAccess> da;
|
||||
if (p_path.begins_with("res://")) {
|
||||
da = create(ACCESS_RESOURCES);
|
||||
} else if (p_path.begins_with("user://")) {
|
||||
da = create(ACCESS_USERDATA);
|
||||
} else {
|
||||
da = create(ACCESS_FILESYSTEM);
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
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, vformat("Cannot create DirAccess for path '%s'.", p_path));
|
||||
Error err = da->change_dir(p_path);
|
||||
if (r_error) {
|
||||
*r_error = err;
|
||||
}
|
||||
if (err != OK) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return da;
|
||||
}
|
||||
|
||||
Ref<DirAccess> DirAccess::_open(const String &p_path) {
|
||||
Error err = OK;
|
||||
Ref<DirAccess> da = open(p_path, &err);
|
||||
last_dir_open_error = err;
|
||||
if (err) {
|
||||
return Ref<DirAccess>();
|
||||
}
|
||||
return da;
|
||||
}
|
||||
|
||||
int DirAccess::_get_drive_count() {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
return d->get_drive_count();
|
||||
}
|
||||
|
||||
String DirAccess::get_drive_name(int p_idx) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
return d->get_drive(p_idx);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->make_dir(p_dir);
|
||||
}
|
||||
|
||||
Error DirAccess::make_dir_recursive_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->make_dir_recursive(p_dir);
|
||||
}
|
||||
|
||||
bool DirAccess::dir_exists_absolute(const String &p_dir) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_dir);
|
||||
return d->dir_exists(p_dir);
|
||||
}
|
||||
|
||||
Error DirAccess::copy_absolute(const String &p_from, const String &p_to, int p_chmod_flags) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
// Support copying from res:// to user:// etc.
|
||||
String from = ProjectSettings::get_singleton()->globalize_path(p_from);
|
||||
String to = ProjectSettings::get_singleton()->globalize_path(p_to);
|
||||
return d->copy(from, to, p_chmod_flags);
|
||||
}
|
||||
|
||||
Error DirAccess::rename_absolute(const String &p_from, const String &p_to) {
|
||||
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
String from = ProjectSettings::get_singleton()->globalize_path(p_from);
|
||||
String to = ProjectSettings::get_singleton()->globalize_path(p_to);
|
||||
return d->rename(from, to);
|
||||
}
|
||||
|
||||
Error DirAccess::remove_absolute(const String &p_path) {
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(p_path);
|
||||
return d->remove(p_path);
|
||||
}
|
||||
|
||||
Ref<DirAccess> DirAccess::create(AccessType p_access) {
|
||||
Ref<DirAccess> da = create_func[p_access] ? create_func[p_access]() : nullptr;
|
||||
if (da.is_valid()) {
|
||||
da->_access_type = p_access;
|
||||
|
||||
// for ACCESS_RESOURCES and ACCESS_FILESYSTEM, current_dir already defaults to where game was started
|
||||
// in case current directory is force changed elsewhere for ACCESS_RESOURCES
|
||||
if (p_access == ACCESS_RESOURCES) {
|
||||
da->change_dir("res://");
|
||||
} else if (p_access == ACCESS_USERDATA) {
|
||||
da->change_dir("user://");
|
||||
}
|
||||
}
|
||||
|
||||
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().remove_chars("-T:");
|
||||
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;
|
||||
}
|
||||
|
||||
String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
|
||||
Ref<DirAccess> d = DirAccess::create(p_access);
|
||||
if (d.is_null()) {
|
||||
return p_path;
|
||||
}
|
||||
|
||||
d->change_dir(p_path);
|
||||
String full = d->get_current_dir();
|
||||
return full;
|
||||
}
|
||||
|
||||
Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flags) {
|
||||
ERR_FAIL_COND_V_MSG(p_from == p_to, ERR_INVALID_PARAMETER, "Source and destination path are equal.");
|
||||
|
||||
//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
|
||||
Error err;
|
||||
{
|
||||
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
|
||||
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, vformat("Failed to open '%s'.", p_to));
|
||||
|
||||
const size_t copy_buffer_limit = 65536; // 64 KB
|
||||
|
||||
fsrc->seek_end(0);
|
||||
uint64_t size = fsrc->get_position();
|
||||
fsrc->seek(0);
|
||||
err = OK;
|
||||
size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit);
|
||||
LocalVector<uint8_t> buffer;
|
||||
buffer.resize(buffer_size);
|
||||
while (size > 0) {
|
||||
if (fsrc->get_error() != OK) {
|
||||
err = fsrc->get_error();
|
||||
break;
|
||||
}
|
||||
if (fdst->get_error() != OK) {
|
||||
err = fdst->get_error();
|
||||
break;
|
||||
}
|
||||
|
||||
int bytes_read = fsrc->get_buffer(buffer.ptr(), buffer_size);
|
||||
if (bytes_read <= 0) {
|
||||
err = FAILED;
|
||||
break;
|
||||
}
|
||||
fdst->store_buffer(buffer.ptr(), bytes_read);
|
||||
|
||||
size -= bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == OK && p_chmod_flags != -1) {
|
||||
err = FileAccess::set_unix_permissions(p_to, p_chmod_flags);
|
||||
// If running on a platform with no chmod support (i.e., Windows), don't fail
|
||||
if (err == ERR_UNAVAILABLE) {
|
||||
err = OK;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
// Changes dir for the current scope, returning back to the original dir
|
||||
// when scope exits
|
||||
class DirChanger {
|
||||
DirAccess *da;
|
||||
String original_dir;
|
||||
|
||||
public:
|
||||
DirChanger(DirAccess *p_da, const String &p_dir) :
|
||||
da(p_da),
|
||||
original_dir(p_da->get_current_dir()) {
|
||||
p_da->change_dir(p_dir);
|
||||
}
|
||||
|
||||
~DirChanger() {
|
||||
da->change_dir(original_dir);
|
||||
}
|
||||
};
|
||||
|
||||
Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int p_chmod_flags, bool p_copy_links) {
|
||||
List<String> dirs;
|
||||
|
||||
String curdir = get_current_dir();
|
||||
list_dir_begin();
|
||||
String n = get_next();
|
||||
while (!n.is_empty()) {
|
||||
if (n != "." && n != "..") {
|
||||
if (p_copy_links && is_link(get_current_dir().path_join(n))) {
|
||||
Error err = p_target_da->create_link(read_link(get_current_dir().path_join(n)), p_to + n);
|
||||
if (err) {
|
||||
ERR_PRINT(vformat("Failed to copy symlink \"%s\".", n));
|
||||
}
|
||||
} else if (current_is_dir()) {
|
||||
dirs.push_back(n);
|
||||
} else {
|
||||
const String &rel_path = n;
|
||||
if (!n.is_relative_path()) {
|
||||
list_dir_end();
|
||||
ERR_FAIL_V_MSG(ERR_BUG, vformat("BUG: \"%s\" is not a relative path.", n));
|
||||
}
|
||||
Error err = copy(get_current_dir().path_join(n), p_to + rel_path, p_chmod_flags);
|
||||
if (err) {
|
||||
list_dir_end();
|
||||
ERR_FAIL_V_MSG(err, vformat("Failed to copy file \"%s\".", n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n = get_next();
|
||||
}
|
||||
|
||||
list_dir_end();
|
||||
|
||||
for (const String &rel_path : dirs) {
|
||||
String target_dir = p_to + rel_path;
|
||||
if (!p_target_da->dir_exists(target_dir)) {
|
||||
Error err = p_target_da->make_dir(target_dir);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", target_dir));
|
||||
}
|
||||
|
||||
Error err = change_dir(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) {
|
||||
change_dir("..");
|
||||
ERR_FAIL_V_MSG(err, "Failed to copy recursively.");
|
||||
}
|
||||
err = change_dir("..");
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back.");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags, bool p_copy_links) {
|
||||
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
|
||||
|
||||
Ref<DirAccess> target_da = DirAccess::create_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, vformat("Cannot create directory '%s'.", p_to));
|
||||
}
|
||||
|
||||
if (!p_to.ends_with("/")) {
|
||||
p_to = p_to + "/";
|
||||
}
|
||||
|
||||
DirChanger dir_changer(this, p_from);
|
||||
Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool DirAccess::exists(const String &p_dir) {
|
||||
Ref<DirAccess> da = DirAccess::create_for_path(p_dir);
|
||||
return da->change_dir(p_dir) == OK;
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_files() {
|
||||
return _get_contents(false);
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_files_at(const String &p_path) {
|
||||
Ref<DirAccess> da = DirAccess::open(p_path);
|
||||
ERR_FAIL_COND_V_MSG(da.is_null(), PackedStringArray(), vformat("Couldn't open directory at path \"%s\".", p_path));
|
||||
return da->get_files();
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_directories() {
|
||||
return _get_contents(true);
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::get_directories_at(const String &p_path) {
|
||||
Ref<DirAccess> da = DirAccess::open(p_path);
|
||||
ERR_FAIL_COND_V_MSG(da.is_null(), PackedStringArray(), vformat("Couldn't open directory at path \"%s\".", p_path));
|
||||
return da->get_directories();
|
||||
}
|
||||
|
||||
PackedStringArray DirAccess::_get_contents(bool p_directories) {
|
||||
PackedStringArray ret;
|
||||
|
||||
list_dir_begin();
|
||||
String s = _get_next();
|
||||
while (!s.is_empty()) {
|
||||
if (current_is_dir() == p_directories) {
|
||||
ret.append(s);
|
||||
}
|
||||
s = _get_next();
|
||||
}
|
||||
|
||||
ret.sort();
|
||||
return ret;
|
||||
}
|
||||
|
||||
String DirAccess::_get_next() {
|
||||
String next = get_next();
|
||||
while (!next.is_empty() && ((!include_navigational && (next == "." || next == "..")) || (!include_hidden && current_is_hidden()))) {
|
||||
next = get_next();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void DirAccess::set_include_navigational(bool p_enable) {
|
||||
include_navigational = p_enable;
|
||||
}
|
||||
|
||||
bool DirAccess::get_include_navigational() const {
|
||||
return include_navigational;
|
||||
}
|
||||
|
||||
void DirAccess::set_include_hidden(bool p_enable) {
|
||||
include_hidden = p_enable;
|
||||
}
|
||||
|
||||
bool DirAccess::get_include_hidden() const {
|
||||
return include_hidden;
|
||||
}
|
||||
|
||||
bool DirAccess::is_case_sensitive(const String &p_path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirAccess::is_equivalent(const String &p_path_a, const String &p_path_b) const {
|
||||
return p_path_a == p_path_b;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
ClassDB::bind_method(D_METHOD("get_files"), &DirAccess::get_files);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_files_at", "path"), &DirAccess::get_files_at);
|
||||
ClassDB::bind_method(D_METHOD("get_directories"), &DirAccess::get_directories);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_directories_at", "path"), &DirAccess::get_directories_at);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_drive_count"), &DirAccess::_get_drive_count);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("get_drive_name", "idx"), &DirAccess::get_drive_name);
|
||||
ClassDB::bind_method(D_METHOD("get_current_drive"), &DirAccess::get_current_drive);
|
||||
ClassDB::bind_method(D_METHOD("change_dir", "to_dir"), &DirAccess::change_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_current_dir", "include_drive"), &DirAccess::get_current_dir, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("make_dir", "path"), &DirAccess::make_dir);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("make_dir_absolute", "path"), &DirAccess::make_dir_absolute);
|
||||
ClassDB::bind_method(D_METHOD("make_dir_recursive", "path"), &DirAccess::make_dir_recursive);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("make_dir_recursive_absolute", "path"), &DirAccess::make_dir_recursive_absolute);
|
||||
ClassDB::bind_method(D_METHOD("file_exists", "path"), &DirAccess::file_exists);
|
||||
ClassDB::bind_method(D_METHOD("dir_exists", "path"), &DirAccess::dir_exists);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("dir_exists_absolute", "path"), &DirAccess::dir_exists_absolute);
|
||||
ClassDB::bind_method(D_METHOD("get_space_left"), &DirAccess::get_space_left);
|
||||
ClassDB::bind_method(D_METHOD("copy", "from", "to", "chmod_flags"), &DirAccess::copy, DEFVAL(-1));
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("copy_absolute", "from", "to", "chmod_flags"), &DirAccess::copy_absolute, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("rename", "from", "to"), &DirAccess::rename);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("rename_absolute", "from", "to"), &DirAccess::rename_absolute);
|
||||
ClassDB::bind_method(D_METHOD("remove", "path"), &DirAccess::remove);
|
||||
ClassDB::bind_static_method("DirAccess", D_METHOD("remove_absolute", "path"), &DirAccess::remove_absolute);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_link", "path"), &DirAccess::is_link);
|
||||
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);
|
||||
ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_filesystem_type"), &DirAccess::get_filesystem_type);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive);
|
||||
ClassDB::bind_method(D_METHOD("is_equivalent", "path_a", "path_b"), &DirAccess::is_equivalent);
|
||||
|
||||
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();
|
||||
}
|
||||
176
core/io/dir_access.h
Normal file
176
core/io/dir_access.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/**************************************************************************/
|
||||
/* dir_access.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
//@ TODO, excellent candidate for THREAD_SAFE MACRO, should go through all these and add THREAD_SAFE where it applies
|
||||
class DirAccess : public RefCounted {
|
||||
GDCLASS(DirAccess, RefCounted);
|
||||
|
||||
public:
|
||||
enum AccessType : int32_t {
|
||||
ACCESS_RESOURCES,
|
||||
ACCESS_USERDATA,
|
||||
ACCESS_FILESYSTEM,
|
||||
ACCESS_MAX
|
||||
};
|
||||
|
||||
typedef Ref<DirAccess> (*CreateFunc)();
|
||||
|
||||
private:
|
||||
AccessType _access_type = ACCESS_FILESYSTEM;
|
||||
static CreateFunc create_func[ACCESS_MAX]; ///< set this to instance a filesystem object
|
||||
static Ref<DirAccess> _open(const String &p_path);
|
||||
|
||||
Error _copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int p_chmod_flags, bool p_copy_links);
|
||||
PackedStringArray _get_contents(bool p_directories);
|
||||
|
||||
static inline thread_local Error last_dir_open_error = OK;
|
||||
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();
|
||||
|
||||
String _get_root_path() const;
|
||||
virtual String _get_root_string() const;
|
||||
|
||||
AccessType get_access_type() const;
|
||||
virtual String fix_path(const String &p_path) const;
|
||||
|
||||
template <typename T>
|
||||
static Ref<DirAccess> _create_builtin() {
|
||||
return memnew(T);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual Error list_dir_begin() = 0; ///< This starts dir listing
|
||||
virtual String get_next() = 0;
|
||||
virtual bool current_is_dir() const = 0;
|
||||
virtual bool current_is_hidden() const = 0;
|
||||
|
||||
virtual void list_dir_end() = 0; ///<
|
||||
|
||||
virtual int get_drive_count() = 0;
|
||||
virtual String get_drive(int p_drive) = 0;
|
||||
virtual int get_current_drive();
|
||||
virtual bool drives_are_shortcuts();
|
||||
|
||||
virtual Error change_dir(String p_dir) = 0; ///< can be relative or absolute, return false on success
|
||||
virtual String get_current_dir(bool p_include_drive = true) const = 0; ///< return current dir location
|
||||
virtual Error make_dir(String p_dir) = 0;
|
||||
virtual Error make_dir_recursive(const String &p_dir);
|
||||
virtual Error erase_contents_recursive(); //super dangerous, use with care!
|
||||
|
||||
virtual bool file_exists(String p_file) = 0;
|
||||
virtual bool dir_exists(String p_dir) = 0;
|
||||
virtual bool is_readable(String p_dir) { return true; }
|
||||
virtual bool is_writable(String p_dir) { return true; }
|
||||
static bool exists(const String &p_dir);
|
||||
virtual uint64_t get_space_left() = 0;
|
||||
|
||||
Error copy_dir(const String &p_from, String p_to, int p_chmod_flags = -1, bool p_copy_links = false);
|
||||
virtual Error copy(const String &p_from, const String &p_to, int p_chmod_flags = -1);
|
||||
virtual Error rename(String p_from, String p_to) = 0;
|
||||
virtual Error remove(String p_name) = 0;
|
||||
|
||||
virtual bool is_link(String p_file) = 0;
|
||||
virtual String read_link(String p_file) = 0;
|
||||
virtual Error create_link(String p_source, String p_target) = 0;
|
||||
|
||||
// Meant for editor code when we want to quickly remove a file without custom
|
||||
// handling (e.g. removing a cache file).
|
||||
static void remove_file_or_error(const String &p_path) {
|
||||
Ref<DirAccess> da = create(ACCESS_FILESYSTEM);
|
||||
if (da->file_exists(p_path)) {
|
||||
if (da->remove(p_path) != OK) {
|
||||
ERR_FAIL_MSG(vformat("Cannot remove file or directory: '%s'.", p_path));
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_MSG(vformat("Cannot remove non-existent file or directory: '%s'.", p_path));
|
||||
}
|
||||
}
|
||||
|
||||
virtual String get_filesystem_type() const = 0;
|
||||
static String get_full_path(const String &p_path, AccessType p_access);
|
||||
static Ref<DirAccess> create_for_path(const String &p_path);
|
||||
|
||||
static Ref<DirAccess> create(AccessType p_access);
|
||||
static Error get_open_error();
|
||||
|
||||
template <typename T>
|
||||
static void make_default(AccessType p_access) {
|
||||
create_func[p_access] = _create_builtin<T>;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
static Error make_dir_absolute(const String &p_dir);
|
||||
static Error make_dir_recursive_absolute(const String &p_dir);
|
||||
static bool dir_exists_absolute(const String &p_dir);
|
||||
|
||||
static Error copy_absolute(const String &p_from, const String &p_to, int p_chmod_flags = -1);
|
||||
static Error rename_absolute(const String &p_from, const String &p_to);
|
||||
static Error remove_absolute(const String &p_path);
|
||||
|
||||
PackedStringArray get_files();
|
||||
static PackedStringArray get_files_at(const String &p_path);
|
||||
PackedStringArray get_directories();
|
||||
static PackedStringArray get_directories_at(const String &p_path);
|
||||
String _get_next();
|
||||
|
||||
void set_include_navigational(bool p_enable);
|
||||
bool get_include_navigational() const;
|
||||
void set_include_hidden(bool p_enable);
|
||||
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; }
|
||||
virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const;
|
||||
|
||||
public:
|
||||
DirAccess() {}
|
||||
virtual ~DirAccess();
|
||||
};
|
||||
47
core/io/dtls_server.cpp
Normal file
47
core/io/dtls_server.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/**************************************************************************/
|
||||
/* dtls_server.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 "dtls_server.h"
|
||||
|
||||
DTLSServer *DTLSServer::create(bool p_notify_postinitialize) {
|
||||
if (_create) {
|
||||
return _create(p_notify_postinitialize);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DTLSServer::is_available() {
|
||||
return available;
|
||||
}
|
||||
|
||||
void DTLSServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("setup", "server_options"), &DTLSServer::setup);
|
||||
ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection);
|
||||
}
|
||||
54
core/io/dtls_server.h
Normal file
54
core/io/dtls_server.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/**************************************************************************/
|
||||
/* dtls_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/net_socket.h"
|
||||
#include "core/io/packet_peer_dtls.h"
|
||||
|
||||
class DTLSServer : public RefCounted {
|
||||
GDCLASS(DTLSServer, RefCounted);
|
||||
|
||||
protected:
|
||||
static inline DTLSServer *(*_create)(bool p_notify_postinitialize) = nullptr;
|
||||
static void _bind_methods();
|
||||
|
||||
static inline bool available = false;
|
||||
|
||||
public:
|
||||
static bool is_available();
|
||||
static DTLSServer *create(bool p_notify_postinitialize = true);
|
||||
|
||||
virtual Error setup(Ref<TLSOptions> p_options) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer) = 0;
|
||||
|
||||
DTLSServer() {}
|
||||
};
|
||||
112
core/io/file_access.compat.inc
Normal file
112
core/io/file_access.compat.inc
Normal 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
|
||||
1059
core/io/file_access.cpp
Normal file
1059
core/io/file_access.cpp
Normal file
File diff suppressed because it is too large
Load Diff
283
core/io/file_access.h
Normal file
283
core/io/file_access.h
Normal file
@@ -0,0 +1,283 @@
|
||||
/**************************************************************************/
|
||||
/* file_access.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/math/math_defs.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
/**
|
||||
* Multi-Platform abstraction for accessing to files.
|
||||
*/
|
||||
|
||||
class FileAccess : public RefCounted {
|
||||
GDCLASS(FileAccess, RefCounted);
|
||||
|
||||
public:
|
||||
enum AccessType : int32_t {
|
||||
ACCESS_RESOURCES,
|
||||
ACCESS_USERDATA,
|
||||
ACCESS_FILESYSTEM,
|
||||
ACCESS_PIPE,
|
||||
ACCESS_MAX
|
||||
};
|
||||
|
||||
enum ModeFlags : int32_t {
|
||||
READ = 1,
|
||||
WRITE = 2,
|
||||
READ_WRITE = 3,
|
||||
WRITE_READ = 7,
|
||||
SKIP_PACK = 16,
|
||||
};
|
||||
|
||||
enum UnixPermissionFlags : int32_t {
|
||||
UNIX_EXECUTE_OTHER = 0x001,
|
||||
UNIX_WRITE_OTHER = 0x002,
|
||||
UNIX_READ_OTHER = 0x004,
|
||||
UNIX_EXECUTE_GROUP = 0x008,
|
||||
UNIX_WRITE_GROUP = 0x010,
|
||||
UNIX_READ_GROUP = 0x020,
|
||||
UNIX_EXECUTE_OWNER = 0x040,
|
||||
UNIX_WRITE_OWNER = 0x080,
|
||||
UNIX_READ_OWNER = 0x100,
|
||||
UNIX_RESTRICTED_DELETE = 0x200,
|
||||
UNIX_SET_GROUP_ID = 0x400,
|
||||
UNIX_SET_USER_ID = 0x800,
|
||||
};
|
||||
|
||||
enum CompressionMode : int32_t {
|
||||
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
|
||||
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
|
||||
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
|
||||
COMPRESSION_GZIP = Compression::MODE_GZIP,
|
||||
COMPRESSION_BROTLI = Compression::MODE_BROTLI,
|
||||
};
|
||||
|
||||
typedef void (*FileCloseFailNotify)(const String &);
|
||||
|
||||
typedef Ref<FileAccess> (*CreateFunc)();
|
||||
#ifdef BIG_ENDIAN_ENABLED
|
||||
bool big_endian = true;
|
||||
#else
|
||||
bool big_endian = false;
|
||||
#endif
|
||||
bool real_is_double = false;
|
||||
|
||||
virtual BitField<UnixPermissionFlags> _get_unix_permissions(const String &p_file) = 0;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<UnixPermissionFlags> p_permissions) = 0;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) = 0;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) = 0;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) = 0;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
AccessType get_access_type() const;
|
||||
virtual String fix_path(const String &p_path) const;
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) = 0; ///< open a file
|
||||
virtual uint64_t _get_modified_time(const String &p_file) = 0;
|
||||
virtual uint64_t _get_access_time(const String &p_file) = 0;
|
||||
virtual int64_t _get_size(const String &p_file) = 0;
|
||||
virtual void _set_access_type(AccessType p_access);
|
||||
|
||||
static inline FileCloseFailNotify close_fail_notify = nullptr;
|
||||
|
||||
#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 inline bool backup_save = false;
|
||||
static inline thread_local Error last_file_open_error = OK;
|
||||
|
||||
AccessType _access_type = ACCESS_FILESYSTEM;
|
||||
static inline CreateFunc create_func[ACCESS_MAX]; /** default file access creation function for a platform */
|
||||
template <typename T>
|
||||
static Ref<FileAccess> _create_builtin() {
|
||||
return memnew(T);
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
virtual bool is_open() const = 0; ///< true when file is open
|
||||
|
||||
virtual String get_path() const { return ""; } /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const { return ""; } /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) = 0; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) = 0; ///< seek from the end of file with negative offset
|
||||
virtual uint64_t get_position() const = 0; ///< get position in the file
|
||||
virtual uint64_t get_length() const = 0; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const = 0; ///< reading passed EOF
|
||||
|
||||
virtual uint8_t get_8() const; ///< 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 = 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;
|
||||
virtual Vector<String> get_csv_line(const String &p_delim = ",") const;
|
||||
String get_as_text(bool p_skip_cr = false) const;
|
||||
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.
|
||||
*/
|
||||
virtual void set_big_endian(bool p_big_endian) { big_endian = p_big_endian; }
|
||||
inline bool is_big_endian() const { return big_endian; }
|
||||
|
||||
virtual Error get_error() const = 0; ///< get last error
|
||||
|
||||
virtual Error resize(int64_t p_length) = 0;
|
||||
virtual void flush() = 0;
|
||||
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 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 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 bool store_pascal_string(const String &p_string);
|
||||
virtual String get_pascal_string();
|
||||
|
||||
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);
|
||||
|
||||
bool store_var(const Variant &p_var, bool p_full_objects = false);
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual bool file_exists(const String &p_name) = 0; ///< return true if a file exists
|
||||
|
||||
virtual Error reopen(const String &p_path, int p_mode_flags); ///< does not change the AccessType
|
||||
|
||||
static 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, 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();
|
||||
|
||||
static CreateFunc get_create_func(AccessType p_access);
|
||||
static bool exists(const String &p_name); ///< return true if a file exists
|
||||
static uint64_t get_modified_time(const String &p_file);
|
||||
static uint64_t get_access_time(const String &p_file);
|
||||
static int64_t get_size(const String &p_file);
|
||||
static BitField<FileAccess::UnixPermissionFlags> get_unix_permissions(const String &p_file);
|
||||
static Error set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions);
|
||||
|
||||
static bool get_hidden_attribute(const String &p_file);
|
||||
static Error set_hidden_attribute(const String &p_file, bool p_hidden);
|
||||
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 String get_md5(const String &p_file);
|
||||
static String get_sha256(const String &p_file);
|
||||
static String get_multiple_md5(const Vector<String> &p_file);
|
||||
|
||||
static Vector<uint8_t> get_file_as_bytes(const String &p_path, Error *r_error = nullptr);
|
||||
static String get_file_as_string(const String &p_path, Error *r_error = nullptr);
|
||||
|
||||
static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path, &last_file_open_error); }
|
||||
static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path, &last_file_open_error); }
|
||||
|
||||
template <typename T>
|
||||
static void make_default(AccessType p_access) {
|
||||
create_func[p_access] = _create_builtin<T>;
|
||||
}
|
||||
|
||||
public:
|
||||
FileAccess() {}
|
||||
virtual ~FileAccess();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(FileAccess::CompressionMode);
|
||||
VARIANT_ENUM_CAST(FileAccess::ModeFlags);
|
||||
VARIANT_BITFIELD_CAST(FileAccess::UnixPermissionFlags);
|
||||
413
core/io/file_access_compressed.cpp
Normal file
413
core/io/file_access_compressed.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_compressed.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 "file_access_compressed.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);
|
||||
|
||||
cmode = p_mode;
|
||||
block_size = p_block_size;
|
||||
}
|
||||
|
||||
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, 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;
|
||||
uint64_t acc_ofs = f->get_position() + bc * 4;
|
||||
uint32_t max_bs = 0;
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
ReadBlock rb;
|
||||
rb.offset = acc_ofs;
|
||||
rb.csize = f->get_32();
|
||||
acc_ofs += rb.csize;
|
||||
max_bs = MAX(max_bs, rb.csize);
|
||||
read_blocks.push_back(rb);
|
||||
}
|
||||
|
||||
comp_buffer.resize(max_bs);
|
||||
buffer.resize(block_size);
|
||||
read_ptr = buffer.ptrw();
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[0].csize);
|
||||
at_end = false;
|
||||
read_eof = false;
|
||||
read_block_count = bc;
|
||||
read_block_size = read_blocks.size() == 1 ? read_total : block_size;
|
||||
|
||||
const int64_t ret = Compression::decompress(buffer.ptrw(), read_block_size, comp_buffer.ptr(), read_blocks[0].csize, cmode);
|
||||
read_block = 0;
|
||||
read_pos = 0;
|
||||
|
||||
return ret == -1 ? ERR_FILE_CORRUPT : OK;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::open_internal(const String &p_path, int p_mode_flags) {
|
||||
ERR_FAIL_COND_V(p_mode_flags == READ_WRITE, ERR_UNAVAILABLE);
|
||||
_close();
|
||||
|
||||
Error err;
|
||||
f = FileAccess::open(p_path, p_mode_flags, &err);
|
||||
if (err != OK) {
|
||||
//not openable
|
||||
f.unref();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (p_mode_flags & WRITE) {
|
||||
buffer.clear();
|
||||
writing = true;
|
||||
write_pos = 0;
|
||||
write_buffer_size = 256;
|
||||
buffer.resize(256);
|
||||
write_max = 0;
|
||||
write_ptr = buffer.ptrw();
|
||||
|
||||
//don't store anything else unless it's done saving!
|
||||
} else {
|
||||
char rmagic[5];
|
||||
f->get_buffer((uint8_t *)rmagic, 4);
|
||||
rmagic[4] = 0;
|
||||
err = ERR_FILE_UNRECOGNIZED;
|
||||
if (magic != rmagic || (err = open_after_magic(f)) != OK) {
|
||||
f.unref();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::_close() {
|
||||
if (f.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (writing) {
|
||||
//save block table and all compressed blocks
|
||||
|
||||
CharString mgc = magic.utf8();
|
||||
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(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++) {
|
||||
f->store_32(0); //compressed sizes, will update later
|
||||
}
|
||||
|
||||
Vector<int> block_sizes;
|
||||
for (uint32_t i = 0; i < bc; i++) {
|
||||
uint32_t bl = i == (bc - 1) ? write_max % block_size : block_size;
|
||||
uint8_t *bp = &write_ptr[i * block_size];
|
||||
|
||||
Vector<uint8_t> cblock;
|
||||
cblock.resize(Compression::get_max_compressed_buffer_size(bl, cmode));
|
||||
const int64_t compressed_size = Compression::compress(cblock.ptrw(), bp, bl, cmode);
|
||||
ERR_FAIL_COND_MSG(compressed_size < 0, "FileAccessCompressed: Error compressing data.");
|
||||
|
||||
f->store_buffer(cblock.ptr(), (uint64_t)compressed_size);
|
||||
block_sizes.push_back(compressed_size);
|
||||
}
|
||||
|
||||
f->seek(16); //ok write block sizes
|
||||
for (uint32_t i = 0; i < bc; 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
|
||||
|
||||
buffer.clear();
|
||||
|
||||
} else {
|
||||
comp_buffer.clear();
|
||||
buffer.clear();
|
||||
read_blocks.clear();
|
||||
}
|
||||
f.unref();
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::is_open() const {
|
||||
return f.is_valid();
|
||||
}
|
||||
|
||||
String FileAccessCompressed::get_path() const {
|
||||
if (f.is_valid()) {
|
||||
return f->get_path();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String FileAccessCompressed::get_path_absolute() const {
|
||||
if (f.is_valid()) {
|
||||
return f->get_path_absolute();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessCompressed::seek(uint64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
if (writing) {
|
||||
ERR_FAIL_COND(p_position > write_max);
|
||||
|
||||
write_pos = p_position;
|
||||
|
||||
} else {
|
||||
ERR_FAIL_COND(p_position > read_total);
|
||||
if (p_position == read_total) {
|
||||
at_end = true;
|
||||
} else {
|
||||
at_end = false;
|
||||
read_eof = false;
|
||||
uint32_t block_idx = p_position / block_size;
|
||||
if (block_idx != read_block) {
|
||||
read_block = block_idx;
|
||||
f->seek(read_blocks[read_block].offset);
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
|
||||
const int64_t ret = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
|
||||
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
|
||||
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
|
||||
}
|
||||
|
||||
read_pos = p_position % block_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessCompressed::seek_end(int64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
if (writing) {
|
||||
seek(write_max + p_position);
|
||||
} else {
|
||||
seek(read_total + p_position);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_position() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return write_pos;
|
||||
} else {
|
||||
return (uint64_t)read_block * block_size + read_pos;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_length() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return write_max;
|
||||
} else {
|
||||
return read_total;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::eof_reached() const {
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), false, "File must be opened before use.");
|
||||
if (writing) {
|
||||
return false;
|
||||
} else {
|
||||
return read_eof;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
if (p_length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_V(p_dst, -1);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
|
||||
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
|
||||
|
||||
if (at_end) {
|
||||
read_eof = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t dst_idx = 0;
|
||||
while (true) {
|
||||
// Copy over as much of our current block as possible.
|
||||
const uint32_t copied_bytes_count = MIN(p_length - dst_idx, read_block_size - read_pos);
|
||||
memcpy(p_dst + dst_idx, read_ptr + read_pos, copied_bytes_count);
|
||||
dst_idx += copied_bytes_count;
|
||||
read_pos += copied_bytes_count;
|
||||
|
||||
if (dst_idx == p_length) {
|
||||
// We're done! We read back all that was requested.
|
||||
return p_length;
|
||||
}
|
||||
|
||||
// We're not done yet; try reading the next block.
|
||||
read_block++;
|
||||
|
||||
if (read_block >= read_block_count) {
|
||||
// We're done! We read back the whole file.
|
||||
read_block--;
|
||||
at_end = true;
|
||||
if (dst_idx + 1 < p_length) {
|
||||
read_eof = true;
|
||||
}
|
||||
return dst_idx;
|
||||
}
|
||||
|
||||
// Read the next block of compressed data.
|
||||
f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
|
||||
const int64_t ret = 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(ret == -1, -1, "Compressed file is corrupt.");
|
||||
read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
|
||||
read_pos = 0;
|
||||
}
|
||||
|
||||
return p_length;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::get_error() const {
|
||||
return read_eof ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::flush() {
|
||||
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.");
|
||||
|
||||
// compressed files keep data in memory till close()
|
||||
}
|
||||
|
||||
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.");
|
||||
|
||||
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) {
|
||||
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
|
||||
if (fa.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::_get_modified_time(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->get_modified_time(p_file);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessCompressed::_get_access_time(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->get_access_time(p_file);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t FileAccessCompressed::_get_size(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->get_size(p_file);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
BitField<FileAccess::UnixPermissionFlags> FileAccessCompressed::_get_unix_permissions(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_unix_permissions(p_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_unix_permissions(p_file, p_permissions);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::_get_hidden_attribute(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_hidden_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_hidden_attribute(const String &p_file, bool p_hidden) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_hidden_attribute(p_file, p_hidden);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessCompressed::_get_read_only_attribute(const String &p_file) {
|
||||
if (f.is_valid()) {
|
||||
return f->_get_read_only_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessCompressed::_set_read_only_attribute(const String &p_file, bool p_ro) {
|
||||
if (f.is_valid()) {
|
||||
return f->_set_read_only_attribute(p_file, p_ro);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void FileAccessCompressed::close() {
|
||||
_close();
|
||||
}
|
||||
|
||||
FileAccessCompressed::~FileAccessCompressed() {
|
||||
_close();
|
||||
}
|
||||
111
core/io/file_access_compressed.h
Normal file
111
core/io/file_access_compressed.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_compressed.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
class FileAccessCompressed : public FileAccess {
|
||||
GDSOFTCLASS(FileAccessCompressed, FileAccess);
|
||||
Compression::Mode cmode = Compression::MODE_ZSTD;
|
||||
bool writing = false;
|
||||
uint64_t write_pos = 0;
|
||||
uint8_t *write_ptr = nullptr;
|
||||
uint64_t write_buffer_size = 0;
|
||||
uint64_t write_max = 0;
|
||||
uint32_t block_size = 0;
|
||||
mutable bool read_eof = false;
|
||||
mutable bool at_end = false;
|
||||
|
||||
struct ReadBlock {
|
||||
uint32_t csize;
|
||||
uint64_t offset;
|
||||
};
|
||||
|
||||
mutable Vector<uint8_t> comp_buffer;
|
||||
uint8_t *read_ptr = nullptr;
|
||||
mutable uint32_t read_block = 0;
|
||||
uint32_t read_block_count = 0;
|
||||
mutable uint32_t read_block_size = 0;
|
||||
mutable uint64_t read_pos = 0;
|
||||
Vector<ReadBlock> read_blocks;
|
||||
uint64_t read_total = 0;
|
||||
|
||||
String magic = "GCMP";
|
||||
mutable Vector<uint8_t> buffer;
|
||||
Ref<FileAccess> f;
|
||||
|
||||
void _close();
|
||||
|
||||
public:
|
||||
void configure(const String &p_magic, Compression::Mode p_mode = Compression::MODE_ZSTD, uint32_t p_block_size = 4096);
|
||||
|
||||
Error open_after_magic(Ref<FileAccess> p_base);
|
||||
|
||||
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
|
||||
|
||||
virtual String get_path() const override; /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
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 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
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override;
|
||||
virtual uint64_t _get_access_time(const String &p_file) override;
|
||||
virtual int64_t _get_size(const String &p_file) override;
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
FileAccessCompressed() {}
|
||||
virtual ~FileAccessCompressed();
|
||||
};
|
||||
353
core/io/file_access_encrypted.cpp
Normal file
353
core/io/file_access_encrypted.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_encrypted.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 "file_access_encrypted.h"
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
CryptoCore::RandomGenerator *FileAccessEncrypted::_fae_static_rng = nullptr;
|
||||
|
||||
void FileAccessEncrypted::deinitialize() {
|
||||
if (_fae_static_rng) {
|
||||
memdelete(_fae_static_rng);
|
||||
_fae_static_rng = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
eofed = false;
|
||||
use_magic = p_with_magic;
|
||||
|
||||
if (p_mode == MODE_WRITE_AES256) {
|
||||
data.clear();
|
||||
writing = true;
|
||||
file = p_base;
|
||||
key = p_key;
|
||||
if (p_iv.is_empty()) {
|
||||
iv.resize(16);
|
||||
if (unlikely(!_fae_static_rng)) {
|
||||
_fae_static_rng = memnew(CryptoCore::RandomGenerator);
|
||||
if (_fae_static_rng->init() != OK) {
|
||||
memdelete(_fae_static_rng);
|
||||
_fae_static_rng = nullptr;
|
||||
ERR_FAIL_V_MSG(FAILED, "Failed to initialize random number generator.");
|
||||
}
|
||||
}
|
||||
Error err = _fae_static_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;
|
||||
key = p_key;
|
||||
|
||||
if (use_magic) {
|
||||
uint32_t magic = p_base->get_32();
|
||||
ERR_FAIL_COND_V(magic != ENCRYPTED_HEADER_MAGIC, ERR_FILE_UNRECOGNIZED);
|
||||
}
|
||||
|
||||
unsigned char md5d[16];
|
||||
p_base->get_buffer(md5d, 16);
|
||||
length = p_base->get_64();
|
||||
|
||||
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);
|
||||
uint64_t ds = length;
|
||||
if (ds % 16) {
|
||||
ds += 16 - (ds % 16);
|
||||
}
|
||||
data.resize(ds);
|
||||
|
||||
uint64_t blen = p_base->get_buffer(data.ptrw(), ds);
|
||||
ERR_FAIL_COND_V(blen != ds, ERR_FILE_CORRUPT);
|
||||
|
||||
{
|
||||
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.ptrw(), data.ptrw(), data.ptrw());
|
||||
}
|
||||
|
||||
data.resize(length);
|
||||
|
||||
unsigned char hash[16];
|
||||
ERR_FAIL_COND_V(CryptoCore::md5(data.ptr(), data.size(), hash) != OK, ERR_BUG);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(String::md5(hash) != String::md5(md5d), ERR_FILE_CORRUPT, "The MD5 sum of the decrypted file does not match the expected value. It could be that the file is corrupt, or that the provided decryption key is invalid.");
|
||||
|
||||
file = p_base;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::open_and_parse_password(Ref<FileAccess> p_base, const String &p_key, Mode p_mode) {
|
||||
String cs = p_key.md5_text();
|
||||
ERR_FAIL_COND_V(cs.length() != 32, ERR_INVALID_PARAMETER);
|
||||
Vector<uint8_t> key_md5;
|
||||
key_md5.resize(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
key_md5.write[i] = cs[i];
|
||||
}
|
||||
|
||||
return open_and_parse(p_base, key_md5, p_mode);
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::open_internal(const String &p_path, int p_mode_flags) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::_close() {
|
||||
if (file.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (writing) {
|
||||
Vector<uint8_t> compressed;
|
||||
uint64_t len = data.size();
|
||||
if (len % 16) {
|
||||
len += 16 - (len % 16);
|
||||
}
|
||||
|
||||
unsigned char hash[16];
|
||||
ERR_FAIL_COND(CryptoCore::md5(data.ptr(), data.size(), hash) != OK); // Bug?
|
||||
|
||||
compressed.resize(len);
|
||||
memset(compressed.ptrw(), 0, len);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
compressed.write[i] = data[i];
|
||||
}
|
||||
|
||||
CryptoCore::AESContext ctx;
|
||||
ctx.set_encode_key(key.ptrw(), 256);
|
||||
|
||||
if (use_magic) {
|
||||
file->store_32(ENCRYPTED_HEADER_MAGIC);
|
||||
}
|
||||
|
||||
file->store_buffer(hash, 16);
|
||||
file->store_64(data.size());
|
||||
file->store_buffer(iv.ptr(), 16);
|
||||
|
||||
ctx.encrypt_cfb(len, iv.ptrw(), compressed.ptrw(), compressed.ptrw());
|
||||
|
||||
file->store_buffer(compressed.ptr(), compressed.size());
|
||||
data.clear();
|
||||
}
|
||||
|
||||
file.unref();
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::is_open() const {
|
||||
return file.is_valid();
|
||||
}
|
||||
|
||||
String FileAccessEncrypted::get_path() const {
|
||||
if (file.is_valid()) {
|
||||
return file->get_path();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String FileAccessEncrypted::get_path_absolute() const {
|
||||
if (file.is_valid()) {
|
||||
return file->get_path_absolute();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::seek(uint64_t p_position) {
|
||||
if (p_position > get_length()) {
|
||||
p_position = get_length();
|
||||
}
|
||||
|
||||
pos = p_position;
|
||||
eofed = false;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::seek_end(int64_t p_position) {
|
||||
seek(get_length() + p_position);
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::get_position() const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::get_length() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::eof_reached() const {
|
||||
return eofed;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_V(p_dst, -1);
|
||||
|
||||
uint64_t to_copy = MIN(p_length, get_length() - pos);
|
||||
|
||||
memcpy(p_dst, data.ptr() + pos, to_copy);
|
||||
pos += to_copy;
|
||||
|
||||
if (to_copy < p_length) {
|
||||
eofed = true;
|
||||
}
|
||||
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::get_error() const {
|
||||
return eofed ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
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 (!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() {
|
||||
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
|
||||
|
||||
// encrypted files keep data in memory till close()
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::file_exists(const String &p_name) {
|
||||
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
|
||||
if (fa.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::_get_modified_time(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->get_modified_time(p_file);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FileAccessEncrypted::_get_access_time(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->get_access_time(p_file);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t FileAccessEncrypted::_get_size(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->get_size(p_file);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
BitField<FileAccess::UnixPermissionFlags> FileAccessEncrypted::_get_unix_permissions(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_unix_permissions(p_file);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_unix_permissions(p_file, p_permissions);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::_get_hidden_attribute(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_hidden_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_hidden_attribute(const String &p_file, bool p_hidden) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_hidden_attribute(p_file, p_hidden);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
bool FileAccessEncrypted::_get_read_only_attribute(const String &p_file) {
|
||||
if (file.is_valid()) {
|
||||
return file->_get_read_only_attribute(p_file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error FileAccessEncrypted::_set_read_only_attribute(const String &p_file, bool p_ro) {
|
||||
if (file.is_valid()) {
|
||||
return file->_set_read_only_attribute(p_file, p_ro);
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
void FileAccessEncrypted::close() {
|
||||
_close();
|
||||
}
|
||||
|
||||
FileAccessEncrypted::~FileAccessEncrypted() {
|
||||
_close();
|
||||
}
|
||||
110
core/io/file_access_encrypted.h
Normal file
110
core/io/file_access_encrypted.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_encrypted.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
#define ENCRYPTED_HEADER_MAGIC 0x43454447
|
||||
|
||||
class FileAccessEncrypted : public FileAccess {
|
||||
GDSOFTCLASS(FileAccessEncrypted, FileAccess);
|
||||
|
||||
public:
|
||||
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;
|
||||
uint64_t base = 0;
|
||||
uint64_t length = 0;
|
||||
Vector<uint8_t> data;
|
||||
mutable uint64_t pos = 0;
|
||||
mutable bool eofed = false;
|
||||
bool use_magic = true;
|
||||
|
||||
void _close();
|
||||
|
||||
static CryptoCore::RandomGenerator *_fae_static_rng;
|
||||
|
||||
public:
|
||||
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
|
||||
|
||||
virtual String get_path() const override; /// returns the path for the current open file
|
||||
virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
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 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
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override;
|
||||
virtual uint64_t _get_access_time(const String &p_file) override;
|
||||
virtual int64_t _get_size(const String &p_file) override;
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override;
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override;
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override;
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override;
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override;
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
static void deinitialize();
|
||||
|
||||
FileAccessEncrypted() {}
|
||||
~FileAccessEncrypted();
|
||||
};
|
||||
168
core/io/file_access_memory.cpp
Normal file
168
core/io/file_access_memory.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_memory.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 "file_access_memory.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
static HashMap<String, Vector<uint8_t>> *files = nullptr;
|
||||
|
||||
void FileAccessMemory::register_file(const String &p_name, const Vector<uint8_t> &p_data) {
|
||||
if (!files) {
|
||||
files = memnew((HashMap<String, Vector<uint8_t>>));
|
||||
}
|
||||
|
||||
String name;
|
||||
if (ProjectSettings::get_singleton()) {
|
||||
name = ProjectSettings::get_singleton()->globalize_path(p_name);
|
||||
} else {
|
||||
name = p_name;
|
||||
}
|
||||
//name = DirAccess::normalize_path(name);
|
||||
|
||||
(*files)[name] = p_data;
|
||||
}
|
||||
|
||||
void FileAccessMemory::cleanup() {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
memdelete(files);
|
||||
}
|
||||
|
||||
Ref<FileAccess> FileAccessMemory::create() {
|
||||
return memnew(FileAccessMemory);
|
||||
}
|
||||
|
||||
bool FileAccessMemory::file_exists(const String &p_name) {
|
||||
String name = fix_path(p_name);
|
||||
//name = DirAccess::normalize_path(name);
|
||||
|
||||
return files && (files->find(name) != nullptr);
|
||||
}
|
||||
|
||||
Error FileAccessMemory::open_custom(const uint8_t *p_data, uint64_t p_len) {
|
||||
data = (uint8_t *)p_data;
|
||||
length = p_len;
|
||||
pos = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
|
||||
ERR_FAIL_NULL_V(files, ERR_FILE_NOT_FOUND);
|
||||
|
||||
String name = fix_path(p_path);
|
||||
//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, vformat("Can't find file '%s'.", p_path));
|
||||
|
||||
data = E->value.ptrw();
|
||||
length = E->value.size();
|
||||
pos = 0;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool FileAccessMemory::is_open() const {
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
void FileAccessMemory::seek(uint64_t p_position) {
|
||||
ERR_FAIL_NULL(data);
|
||||
pos = p_position;
|
||||
}
|
||||
|
||||
void FileAccessMemory::seek_end(int64_t p_position) {
|
||||
ERR_FAIL_NULL(data);
|
||||
pos = length + p_position;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_position() const {
|
||||
ERR_FAIL_NULL_V(data, 0);
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_length() const {
|
||||
ERR_FAIL_NULL_V(data, 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool FileAccessMemory::eof_reached() const {
|
||||
return pos >= length;
|
||||
}
|
||||
|
||||
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
|
||||
if (!p_length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_V(p_dst, -1);
|
||||
ERR_FAIL_NULL_V(data, -1);
|
||||
|
||||
uint64_t left = length - pos;
|
||||
uint64_t read = MIN(p_length, left);
|
||||
|
||||
if (read < p_length) {
|
||||
WARN_PRINT("Reading less data than requested");
|
||||
}
|
||||
|
||||
memcpy(p_dst, &data[pos], read);
|
||||
pos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
Error FileAccessMemory::get_error() const {
|
||||
return pos >= length ? ERR_FILE_EOF : OK;
|
||||
}
|
||||
|
||||
void FileAccessMemory::flush() {
|
||||
ERR_FAIL_NULL(data);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
uint64_t left = length - pos;
|
||||
uint64_t write = MIN(p_length, left);
|
||||
|
||||
memcpy(&data[pos], p_src, write);
|
||||
pos += write;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(write < p_length, false, "Writing less data than requested.");
|
||||
|
||||
return true;
|
||||
}
|
||||
83
core/io/file_access_memory.h
Normal file
83
core/io/file_access_memory.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_memory.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
class FileAccessMemory : public FileAccess {
|
||||
GDSOFTCLASS(FileAccessMemory, FileAccess);
|
||||
uint8_t *data = nullptr;
|
||||
uint64_t length = 0;
|
||||
mutable uint64_t pos = 0;
|
||||
|
||||
static Ref<FileAccess> create();
|
||||
|
||||
public:
|
||||
static void register_file(const String &p_name, const Vector<uint8_t> &p_data);
|
||||
static void cleanup();
|
||||
|
||||
virtual Error open_custom(const uint8_t *p_data, uint64_t p_len); ///< open a file
|
||||
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
|
||||
|
||||
virtual void seek(uint64_t p_position) override; ///< seek to a given position
|
||||
virtual void seek_end(int64_t p_position) override; ///< seek from the end of file
|
||||
virtual uint64_t get_position() const override; ///< get position in the file
|
||||
virtual uint64_t get_length() const override; ///< get size of the file
|
||||
|
||||
virtual bool eof_reached() const override; ///< reading passed EOF
|
||||
|
||||
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 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
|
||||
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
|
||||
virtual uint64_t _get_access_time(const String &p_file) override { return 0; }
|
||||
virtual int64_t _get_size(const String &p_file) override { return -1; }
|
||||
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; }
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override { return false; }
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; }
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override { return false; }
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; }
|
||||
|
||||
virtual void close() override {}
|
||||
|
||||
FileAccessMemory() {}
|
||||
};
|
||||
672
core/io/file_access_pack.cpp
Normal file
672
core/io/file_access_pack.cpp
Normal file
@@ -0,0 +1,672 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_pack.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 "file_access_pack.h"
|
||||
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/version.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)) {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_FILE_UNRECOGNIZED;
|
||||
}
|
||||
|
||||
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, bool p_bundle) {
|
||||
String simplified_path = p_path.simplify_path().trim_prefix("res://");
|
||||
PathMD5 pmd5(simplified_path.md5_buffer());
|
||||
|
||||
bool exists = files.has(pmd5);
|
||||
|
||||
PackedFile pf;
|
||||
pf.encrypted = p_encrypted;
|
||||
pf.bundle = p_bundle;
|
||||
pf.pack = p_pkg_path;
|
||||
pf.offset = p_ofs;
|
||||
pf.size = p_size;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
pf.md5[i] = p_md5[i];
|
||||
}
|
||||
pf.src = p_src;
|
||||
|
||||
if (!exists || p_replace_files) {
|
||||
files[pmd5] = pf;
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
// 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])) {
|
||||
PackedDir *pd = memnew(PackedDir);
|
||||
pd->name = ds[j];
|
||||
pd->parent = cd;
|
||||
cd->subdirs[pd->name] = pd;
|
||||
cd = pd;
|
||||
} else {
|
||||
cd = cd->subdirs[ds[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
String filename = simplified_path.get_file();
|
||||
// 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 = this;
|
||||
root = memnew(PackedDir);
|
||||
|
||||
add_pack_source(memnew(PackedSourcePCK));
|
||||
}
|
||||
|
||||
void PackedData::_free_packed_dirs(PackedDir *p_dir) {
|
||||
for (const KeyValue<String, PackedDir *> &E : p_dir->subdirs) {
|
||||
_free_packed_dirs(E.value);
|
||||
}
|
||||
memdelete(p_dir);
|
||||
}
|
||||
|
||||
PackedData::~PackedData() {
|
||||
if (singleton == this) {
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
memdelete(sources[i]);
|
||||
}
|
||||
_free_packed_dirs(root);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pck_header_found = false;
|
||||
|
||||
// Search for the header at the start offset - standalone PCK file.
|
||||
f->seek(p_offset);
|
||||
uint32_t magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
pck_header_found = true;
|
||||
}
|
||||
|
||||
// Search for the header in the executable "pck" section - self contained executable.
|
||||
if (!pck_header_found) {
|
||||
// Loading with offset feature not supported for self contained exe files.
|
||||
if (p_offset != 0) {
|
||||
ERR_FAIL_V_MSG(false, "Loading self-contained executable with offset not supported.");
|
||||
}
|
||||
|
||||
int64_t pck_off = OS::get_singleton()->get_embedded_pck_offset();
|
||||
if (pck_off != 0) {
|
||||
// Search for the header, in case PCK start and section have different alignment.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
f->seek(pck_off);
|
||||
magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("PCK header found in executable pck section, loading from offset 0x" + String::num_int64(pck_off - 4, 16));
|
||||
#endif
|
||||
pck_header_found = true;
|
||||
break;
|
||||
}
|
||||
pck_off++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for the header at the end of file - self contained executable.
|
||||
if (!pck_header_found) {
|
||||
// Loading with offset feature not supported for self contained exe files.
|
||||
if (p_offset != 0) {
|
||||
ERR_FAIL_V_MSG(false, "Loading self-contained executable with offset not supported.");
|
||||
}
|
||||
|
||||
f->seek_end();
|
||||
f->seek(f->get_position() - 4);
|
||||
magic = f->get_32();
|
||||
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
f->seek(f->get_position() - 12);
|
||||
uint64_t ds = f->get_64();
|
||||
f->seek(f->get_position() - ds - 8);
|
||||
magic = f->get_32();
|
||||
if (magic == PACK_HEADER_MAGIC) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
print_verbose("PCK header found at the end of executable, loading from offset 0x" + String::num_int64(f->get_position() - 4, 16));
|
||||
#endif
|
||||
pck_header_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pck_header_found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t pck_start_pos = f->get_position() - 4;
|
||||
|
||||
// Read header.
|
||||
uint32_t version = f->get_32();
|
||||
uint32_t ver_major = f->get_32();
|
||||
uint32_t ver_minor = f->get_32();
|
||||
uint32_t ver_patch = f->get_32(); // Not used for validation.
|
||||
|
||||
ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION_V3 && version != PACK_FORMAT_VERSION_V2, false, vformat("Pack version unsupported: %d.", version));
|
||||
ERR_FAIL_COND_V_MSG(ver_major > GODOT_VERSION_MAJOR || (ver_major == GODOT_VERSION_MAJOR && ver_minor > GODOT_VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.%d.", ver_major, ver_minor, ver_patch));
|
||||
|
||||
uint32_t pack_flags = f->get_32();
|
||||
bool enc_directory = (pack_flags & PACK_DIR_ENCRYPTED);
|
||||
bool rel_filebase = (pack_flags & PACK_REL_FILEBASE); // Note: Always enabled for V3.
|
||||
bool sparse_bundle = (pack_flags & PACK_SPARSE_BUNDLE);
|
||||
|
||||
uint64_t file_base = f->get_64();
|
||||
if ((version == PACK_FORMAT_VERSION_V3) || (version == PACK_FORMAT_VERSION_V2 && rel_filebase)) {
|
||||
file_base += pck_start_pos;
|
||||
}
|
||||
|
||||
if (version == PACK_FORMAT_VERSION_V3) {
|
||||
// V3: Read directory offset and skip reserved part of the header.
|
||||
uint64_t dir_offset = f->get_64() + pck_start_pos;
|
||||
f->seek(dir_offset);
|
||||
} else if (version == PACK_FORMAT_VERSION_V2) {
|
||||
// V2: Directory directly after the header.
|
||||
for (int i = 0; i < 16; i++) {
|
||||
f->get_32(); // Reserved.
|
||||
}
|
||||
}
|
||||
|
||||
// Read directory.
|
||||
int file_count = f->get_32();
|
||||
if (enc_directory) {
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
ERR_FAIL_COND_V_MSG(fae.is_null(), false, "Can't open encrypted pack directory.");
|
||||
|
||||
Vector<uint8_t> key;
|
||||
key.resize(32);
|
||||
for (int i = 0; i < key.size(); i++) {
|
||||
key.write[i] = script_encryption_key[i];
|
||||
}
|
||||
|
||||
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
|
||||
ERR_FAIL_COND_V_MSG(err, false, "Can't open encrypted pack directory.");
|
||||
f = fae;
|
||||
}
|
||||
|
||||
for (int i = 0; i < file_count; i++) {
|
||||
uint32_t sl = f->get_32();
|
||||
CharString cs;
|
||||
cs.resize_uninitialized(sl + 1);
|
||||
f->get_buffer((uint8_t *)cs.ptr(), sl);
|
||||
cs[sl] = 0;
|
||||
|
||||
String path = String::utf8(cs.ptr(), sl);
|
||||
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();
|
||||
|
||||
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, size, md5, this, p_replace_files, (flags & PACK_FILE_ENCRYPTED), sparse_bundle);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<FileAccess> PackedSourcePCK::get_file(const String &p_path, PackedData::PackedFile *p_file) {
|
||||
return memnew(FileAccessPack(p_path, *p_file));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
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, 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;
|
||||
}
|
||||
|
||||
bool FileAccessPack::is_open() const {
|
||||
if (f.is_valid()) {
|
||||
return f->is_open();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileAccessPack::seek(uint64_t p_position) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
if (p_position > pf.size) {
|
||||
eof = true;
|
||||
} else {
|
||||
eof = false;
|
||||
}
|
||||
|
||||
f->seek(off + p_position);
|
||||
pos = p_position;
|
||||
}
|
||||
|
||||
void FileAccessPack::seek_end(int64_t p_position) {
|
||||
seek(pf.size + p_position);
|
||||
}
|
||||
|
||||
uint64_t FileAccessPack::get_position() const {
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint64_t FileAccessPack::get_length() const {
|
||||
return pf.size;
|
||||
}
|
||||
|
||||
bool FileAccessPack::eof_reached() const {
|
||||
return eof;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (eof) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t to_read = p_length;
|
||||
if (to_read + pos > pf.size) {
|
||||
eof = true;
|
||||
to_read = (int64_t)pf.size - (int64_t)pos;
|
||||
}
|
||||
|
||||
pos += to_read;
|
||||
|
||||
if (to_read <= 0) {
|
||||
return 0;
|
||||
}
|
||||
f->get_buffer(p_dst, to_read);
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
void FileAccessPack::set_big_endian(bool p_big_endian) {
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
|
||||
|
||||
FileAccess::set_big_endian(p_big_endian);
|
||||
f->set_big_endian(p_big_endian);
|
||||
}
|
||||
|
||||
Error FileAccessPack::get_error() const {
|
||||
if (eof) {
|
||||
return ERR_FILE_EOF;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void FileAccessPack::flush() {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileAccessPack::close() {
|
||||
f = Ref<FileAccess>();
|
||||
}
|
||||
|
||||
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) {
|
||||
pf = p_file;
|
||||
if (pf.bundle) {
|
||||
String simplified_path = p_path.simplify_path();
|
||||
f = FileAccess::open(simplified_path, FileAccess::READ | FileAccess::SKIP_PACK);
|
||||
off = 0; // For the sparse pack offset is always zero.
|
||||
} else {
|
||||
f = FileAccess::open(pf.pack, FileAccess::READ);
|
||||
f->seek(pf.offset);
|
||||
off = pf.offset;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
|
||||
|
||||
if (pf.encrypted) {
|
||||
Ref<FileAccessEncrypted> fae;
|
||||
fae.instantiate();
|
||||
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);
|
||||
for (int i = 0; i < key.size(); i++) {
|
||||
key.write[i] = script_encryption_key[i];
|
||||
}
|
||||
|
||||
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
|
||||
ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
|
||||
f = fae;
|
||||
off = 0;
|
||||
}
|
||||
pos = 0;
|
||||
eof = false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// DIR ACCESS
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Error DirAccessPack::list_dir_begin() {
|
||||
list_dirs.clear();
|
||||
list_files.clear();
|
||||
|
||||
for (const KeyValue<String, PackedData::PackedDir *> &E : current->subdirs) {
|
||||
list_dirs.push_back(E.key);
|
||||
}
|
||||
|
||||
for (const String &E : current->files) {
|
||||
list_files.push_back(E);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_next() {
|
||||
if (list_dirs.size()) {
|
||||
cdir = true;
|
||||
String d = list_dirs.front()->get();
|
||||
list_dirs.pop_front();
|
||||
return d;
|
||||
} else if (list_files.size()) {
|
||||
cdir = false;
|
||||
String f = list_files.front()->get();
|
||||
list_files.pop_front();
|
||||
return f;
|
||||
} else {
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
bool DirAccessPack::current_is_dir() const {
|
||||
return cdir;
|
||||
}
|
||||
|
||||
bool DirAccessPack::current_is_hidden() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DirAccessPack::list_dir_end() {
|
||||
list_dirs.clear();
|
||||
list_files.clear();
|
||||
}
|
||||
|
||||
int DirAccessPack::get_drive_count() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_drive(int p_drive) {
|
||||
return "";
|
||||
}
|
||||
|
||||
PackedData::PackedDir *DirAccessPack::_find_dir(const String &p_dir) {
|
||||
String nd = p_dir.replace_char('\\', '/');
|
||||
|
||||
// Special handling since simplify_path() will forbid it
|
||||
if (p_dir == "..") {
|
||||
return current->parent;
|
||||
}
|
||||
|
||||
bool absolute = false;
|
||||
if (nd.begins_with("res://")) {
|
||||
nd = nd.replace_first("res://", "");
|
||||
absolute = true;
|
||||
}
|
||||
|
||||
nd = nd.simplify_path();
|
||||
|
||||
if (nd.is_empty()) {
|
||||
nd = ".";
|
||||
}
|
||||
|
||||
if (nd.begins_with("/")) {
|
||||
nd = nd.replace_first("/", "");
|
||||
absolute = true;
|
||||
}
|
||||
|
||||
Vector<String> paths = nd.split("/");
|
||||
|
||||
PackedData::PackedDir *pd;
|
||||
|
||||
if (absolute) {
|
||||
pd = PackedData::get_singleton()->root;
|
||||
} else {
|
||||
pd = current;
|
||||
}
|
||||
|
||||
for (int i = 0; i < paths.size(); i++) {
|
||||
const String &p = paths[i];
|
||||
if (p == ".") {
|
||||
continue;
|
||||
} else if (p == "..") {
|
||||
if (pd->parent) {
|
||||
pd = pd->parent;
|
||||
}
|
||||
} else if (pd->subdirs.has(p)) {
|
||||
pd = pd->subdirs[p];
|
||||
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return pd;
|
||||
}
|
||||
|
||||
Error DirAccessPack::change_dir(String p_dir) {
|
||||
PackedData::PackedDir *pd = _find_dir(p_dir);
|
||||
if (pd) {
|
||||
current = pd;
|
||||
return OK;
|
||||
} else {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
String DirAccessPack::get_current_dir(bool p_include_drive) const {
|
||||
PackedData::PackedDir *pd = current;
|
||||
String p = current->name;
|
||||
|
||||
while (pd->parent) {
|
||||
pd = pd->parent;
|
||||
p = pd->name.path_join(p);
|
||||
}
|
||||
|
||||
return "res://" + p;
|
||||
}
|
||||
|
||||
bool DirAccessPack::file_exists(String p_file) {
|
||||
PackedData::PackedDir *pd = _find_dir(p_file.get_base_dir());
|
||||
if (!pd) {
|
||||
return false;
|
||||
}
|
||||
return pd->files.has(p_file.get_file());
|
||||
}
|
||||
|
||||
bool DirAccessPack::dir_exists(String p_dir) {
|
||||
return _find_dir(p_dir) != nullptr;
|
||||
}
|
||||
|
||||
Error DirAccessPack::make_dir(String p_dir) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error DirAccessPack::rename(String p_from, String p_to) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error DirAccessPack::remove(String p_name) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
uint64_t DirAccessPack::get_space_left() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String DirAccessPack::get_filesystem_type() const {
|
||||
return "PCK";
|
||||
}
|
||||
|
||||
DirAccessPack::DirAccessPack() {
|
||||
current = PackedData::get_singleton()->root;
|
||||
}
|
||||
298
core/io/file_access_pack.h
Normal file
298
core/io/file_access_pack.h
Normal file
@@ -0,0 +1,298 @@
|
||||
/**************************************************************************/
|
||||
/* file_access_pack.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
// Godot's packed file magic header ("GDPC" in ASCII).
|
||||
#define PACK_HEADER_MAGIC 0x43504447
|
||||
|
||||
#define PACK_FORMAT_VERSION_V2 2
|
||||
#define PACK_FORMAT_VERSION_V3 3
|
||||
|
||||
// The current packed file format version number.
|
||||
#define PACK_FORMAT_VERSION PACK_FORMAT_VERSION_V3
|
||||
|
||||
enum PackFlags {
|
||||
PACK_DIR_ENCRYPTED = 1 << 0,
|
||||
PACK_REL_FILEBASE = 1 << 1,
|
||||
PACK_SPARSE_BUNDLE = 1 << 2,
|
||||
};
|
||||
|
||||
enum PackFileFlags {
|
||||
PACK_FILE_ENCRYPTED = 1 << 0,
|
||||
PACK_FILE_REMOVAL = 1 << 1,
|
||||
};
|
||||
|
||||
class PackSource;
|
||||
|
||||
class PackedData {
|
||||
friend class FileAccessPack;
|
||||
friend class DirAccessPack;
|
||||
friend class PackSource;
|
||||
|
||||
public:
|
||||
struct PackedFile {
|
||||
String pack;
|
||||
uint64_t offset; //if offset is ZERO, the file was ERASED
|
||||
uint64_t size;
|
||||
uint8_t md5[16];
|
||||
PackSource *src = nullptr;
|
||||
bool encrypted;
|
||||
bool bundle;
|
||||
};
|
||||
|
||||
private:
|
||||
struct PackedDir {
|
||||
PackedDir *parent = nullptr;
|
||||
String name;
|
||||
HashMap<String, PackedDir *> subdirs;
|
||||
HashSet<String> files;
|
||||
};
|
||||
|
||||
struct PathMD5 {
|
||||
uint64_t a = 0;
|
||||
uint64_t b = 0;
|
||||
|
||||
bool operator==(const PathMD5 &p_val) const {
|
||||
return (a == p_val.a) && (b == p_val.b);
|
||||
}
|
||||
static uint32_t hash(const PathMD5 &p_val) {
|
||||
uint32_t h = hash_murmur3_one_32(p_val.a);
|
||||
return hash_fmix32(hash_murmur3_one_32(p_val.b, h));
|
||||
}
|
||||
|
||||
PathMD5() {}
|
||||
|
||||
explicit PathMD5(const Vector<uint8_t> &p_buf) {
|
||||
a = *((uint64_t *)&p_buf[0]);
|
||||
b = *((uint64_t *)&p_buf[8]);
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<PathMD5, PackedFile, PathMD5> files;
|
||||
|
||||
Vector<PackSource *> sources;
|
||||
|
||||
PackedDir *root = nullptr;
|
||||
|
||||
static inline PackedData *singleton = nullptr;
|
||||
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, bool p_bundle = 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; }
|
||||
|
||||
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);
|
||||
|
||||
_FORCE_INLINE_ int64_t get_size(const String &p_path);
|
||||
|
||||
_FORCE_INLINE_ Ref<DirAccess> try_open_directory(const String &p_path);
|
||||
_FORCE_INLINE_ bool has_directory(const String &p_path);
|
||||
|
||||
PackedData();
|
||||
~PackedData();
|
||||
};
|
||||
|
||||
class PackSource {
|
||||
public:
|
||||
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) = 0;
|
||||
virtual Ref<FileAccess> get_file(const String &p_path, PackedData::PackedFile *p_file) = 0;
|
||||
virtual ~PackSource() {}
|
||||
};
|
||||
|
||||
class PackedSourcePCK : public PackSource {
|
||||
public:
|
||||
virtual bool try_open_pack(const String &p_path, bool p_replace_files, uint64_t p_offset) override;
|
||||
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 {
|
||||
GDSOFTCLASS(FileAccessPack, FileAccess);
|
||||
PackedData::PackedFile pf;
|
||||
|
||||
mutable uint64_t pos;
|
||||
mutable bool eof;
|
||||
uint64_t off;
|
||||
|
||||
Ref<FileAccess> f;
|
||||
virtual Error open_internal(const String &p_path, int p_mode_flags) override;
|
||||
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
|
||||
virtual uint64_t _get_access_time(const String &p_file) override { return 0; }
|
||||
virtual int64_t _get_size(const String &p_file) override { return -1; }
|
||||
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
|
||||
virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; }
|
||||
|
||||
virtual bool _get_hidden_attribute(const String &p_file) override { return false; }
|
||||
virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; }
|
||||
virtual bool _get_read_only_attribute(const String &p_file) override { return false; }
|
||||
virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; }
|
||||
|
||||
public:
|
||||
virtual bool is_open() const override;
|
||||
|
||||
virtual void seek(uint64_t p_position) override;
|
||||
virtual void seek_end(int64_t p_position = 0) override;
|
||||
virtual uint64_t get_position() const override;
|
||||
virtual uint64_t get_length() const override;
|
||||
|
||||
virtual bool eof_reached() 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;
|
||||
|
||||
virtual Error get_error() const override;
|
||||
|
||||
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
|
||||
virtual void flush() override;
|
||||
virtual bool store_buffer(const uint8_t *p_src, uint64_t p_length) override;
|
||||
|
||||
virtual bool file_exists(const String &p_name) override;
|
||||
|
||||
virtual void close() override;
|
||||
|
||||
FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file);
|
||||
};
|
||||
|
||||
int64_t PackedData::get_size(const String &p_path) {
|
||||
String simplified_path = p_path.simplify_path();
|
||||
PathMD5 pmd5(simplified_path.md5_buffer());
|
||||
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
|
||||
if (!E) {
|
||||
return -1; // File not found.
|
||||
}
|
||||
if (E->value.offset == 0) {
|
||||
return -1; // File was erased.
|
||||
}
|
||||
return E->value.size;
|
||||
}
|
||||
|
||||
Ref<FileAccess> PackedData::try_open_path(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; // 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().trim_prefix("res://").md5_buffer()));
|
||||
}
|
||||
|
||||
bool PackedData::has_directory(const String &p_path) {
|
||||
Ref<DirAccess> da = try_open_directory(p_path);
|
||||
if (da.is_valid()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class DirAccessPack : public DirAccess {
|
||||
GDSOFTCLASS(DirAccessPack, DirAccess);
|
||||
PackedData::PackedDir *current;
|
||||
|
||||
List<String> list_dirs;
|
||||
List<String> list_files;
|
||||
bool cdir = false;
|
||||
|
||||
PackedData::PackedDir *_find_dir(const String &p_dir);
|
||||
|
||||
public:
|
||||
virtual Error list_dir_begin() override;
|
||||
virtual String get_next() override;
|
||||
virtual bool current_is_dir() const override;
|
||||
virtual bool current_is_hidden() const override;
|
||||
virtual void list_dir_end() override;
|
||||
|
||||
virtual int get_drive_count() override;
|
||||
virtual String get_drive(int p_drive) override;
|
||||
|
||||
virtual Error change_dir(String p_dir) override;
|
||||
virtual String get_current_dir(bool p_include_drive = true) const override;
|
||||
|
||||
virtual bool file_exists(String p_file) override;
|
||||
virtual bool dir_exists(String p_dir) override;
|
||||
|
||||
virtual Error make_dir(String p_dir) override;
|
||||
|
||||
virtual Error rename(String p_from, String p_to) override;
|
||||
virtual Error remove(String p_name) override;
|
||||
|
||||
uint64_t get_space_left() override;
|
||||
|
||||
virtual bool is_link(String p_file) override { return false; }
|
||||
virtual String read_link(String p_file) override { return p_file; }
|
||||
virtual Error create_link(String p_source, String p_target) override { return FAILED; }
|
||||
|
||||
virtual String get_filesystem_type() const override;
|
||||
|
||||
DirAccessPack();
|
||||
};
|
||||
|
||||
Ref<DirAccess> PackedData::try_open_directory(const String &p_path) {
|
||||
Ref<DirAccess> da = memnew(DirAccessPack());
|
||||
if (da->change_dir(p_path) != OK) {
|
||||
da = Ref<DirAccess>();
|
||||
}
|
||||
return da;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user