summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/engine.cpp2
-rw-r--r--core/config/project_settings.cpp1
-rw-r--r--core/core_bind.cpp2
-rw-r--r--core/core_bind.h8
-rw-r--r--core/core_builders.py1
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/io/resource.cpp17
-rw-r--r--core/io/resource.h2
-rw-r--r--core/io/resource_format_binary.cpp31
-rw-r--r--core/io/resource_format_binary.h1
-rw-r--r--core/io/resource_loader.cpp14
-rw-r--r--core/io/resource_loader.h8
-rw-r--r--core/math/geometry_3d.cpp2
-rw-r--r--core/object/class_db.cpp2
-rw-r--r--core/object/method_bind.h28
-rw-r--r--core/object/object.cpp18
-rw-r--r--core/string/node_path.cpp33
-rw-r--r--core/string/node_path.h2
-rw-r--r--core/string/translation.cpp38
-rw-r--r--core/string/translation.h10
-rw-r--r--core/string/ustring.cpp21
-rw-r--r--core/string/ustring.h37
-rw-r--r--core/variant/callable.cpp32
-rw-r--r--core/variant/callable.h2
-rw-r--r--core/variant/variant_call.cpp2
-rw-r--r--core/variant/variant_internal.h2
-rw-r--r--core/variant/variant_setget.cpp8
-rw-r--r--core/version.h6
28 files changed, 254 insertions, 78 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 2bb8837849..d714ec42c2 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -114,6 +114,8 @@ Dictionary Engine::get_version_info() const {
String hash = String(VERSION_HASH);
dict["hash"] = hash.is_empty() ? String("unknown") : hash;
+ dict["timestamp"] = VERSION_TIMESTAMP;
+
String stringver = String(dict["major"]) + "." + String(dict["minor"]);
if ((int)dict["patch"] != 0) {
stringver += "." + String(dict["patch"]);
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index d9a5a5094a..d1c90ed7e6 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1514,6 +1514,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray());
GLOBAL_DEF_INTERNAL("internationalization/locale/translations", PackedStringArray());
GLOBAL_DEF_INTERNAL("internationalization/locale/translations_pot_files", PackedStringArray());
+ GLOBAL_DEF_INTERNAL("internationalization/locale/translation_add_builtin_strings_to_pot", false);
ProjectSettings::get_singleton()->add_hidden_prefix("input/");
}
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 6dbb0996eb..b5098fc4d2 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -145,6 +145,8 @@ void ResourceLoader::_bind_methods() {
BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE);
BIND_ENUM_CONSTANT(CACHE_MODE_REUSE);
BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE);
+ BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE_DEEP);
+ BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE_DEEP);
}
////// ResourceSaver //////
diff --git a/core/core_bind.h b/core/core_bind.h
index 7b7eb46fd9..d5b8151f1e 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -63,9 +63,11 @@ public:
};
enum CacheMode {
- CACHE_MODE_IGNORE, // Resource and subresources do not use path cache, no path is set into resource.
- CACHE_MODE_REUSE, // Resource and subresources use patch cache, reuse existing loaded resources instead of loading from disk when available.
- CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk.
+ CACHE_MODE_IGNORE,
+ CACHE_MODE_REUSE,
+ CACHE_MODE_REPLACE,
+ CACHE_MODE_IGNORE_DEEP,
+ CACHE_MODE_REPLACE_DEEP,
};
static ResourceLoader *get_singleton() { return singleton; }
diff --git a/core/core_builders.py b/core/core_builders.py
index 8b6b87ad83..61b7bd695c 100644
--- a/core/core_builders.py
+++ b/core/core_builders.py
@@ -2,6 +2,7 @@
All such functions are invoked in a subprocess on Windows to prevent build flakiness.
"""
+
import zlib
from platform_methods import subprocess_main
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 0c96c32187..f96a8873ca 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1415,7 +1415,7 @@ static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data)
#endif
}
-#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name)
+#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr) & gdextension_##m_name)
void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(get_godot_version);
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index daa57be76f..7e8d0b43cd 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -41,7 +41,12 @@
#include <stdio.h>
void Resource::emit_changed() {
- emit_signal(CoreStringNames::get_singleton()->changed);
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ // Let the connection happen on the main thread, later, since signals are not thread-safe.
+ call_deferred("emit_signal", CoreStringNames::get_singleton()->changed);
+ } else {
+ emit_signal(CoreStringNames::get_singleton()->changed);
+ }
}
void Resource::_resource_path_changed() {
@@ -152,12 +157,22 @@ bool Resource::editor_can_reload_from_file() {
}
void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) {
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ // Let the check and connection happen on the main thread, later, since signals are not thread-safe.
+ callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags);
+ return;
+ }
if (!is_connected(CoreStringNames::get_singleton()->changed, p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) {
connect(CoreStringNames::get_singleton()->changed, p_callable, p_flags);
}
}
void Resource::disconnect_changed(const Callable &p_callable) {
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ // Let the check and disconnection happen on the main thread, later, since signals are not thread-safe.
+ callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable);
+ return;
+ }
if (is_connected(CoreStringNames::get_singleton()->changed, p_callable)) {
disconnect(CoreStringNames::get_singleton()->changed, p_callable);
}
diff --git a/core/io/resource.h b/core/io/resource.h
index b885b773ac..f0f686af57 100644
--- a/core/io/resource.h
+++ b/core/io/resource.h
@@ -106,7 +106,7 @@ public:
virtual void set_path(const String &p_path, bool p_take_over = false);
String get_path() const;
- void set_path_cache(const String &p_path); // Set raw path without involving resource cache.
+ virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache.
_FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); }
static String generate_scene_unique_id();
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 20c494516b..17cffb878e 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -430,7 +430,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
path = remaps[path];
}
- Ref<Resource> res = ResourceLoader::load(path, exttype);
+ Ref<Resource> res = ResourceLoader::load(path, exttype, cache_mode_for_external);
if (res.is_null()) {
WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data());
@@ -683,7 +683,7 @@ Error ResourceLoaderBinary::load() {
}
external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap
- external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE);
+ external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, cache_mode_for_external);
if (!external_resources[i].load_token.is_valid()) {
if (!ResourceLoader::get_abort_on_missing_resources()) {
ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
@@ -772,10 +772,12 @@ Error ResourceLoaderBinary::load() {
}
res = Ref<Resource>(r);
- if (!path.is_empty() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it
- } else if (!path.is_resource_file()) {
- r->set_path_cache(path);
+ if (!path.is_empty()) {
+ if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
+ r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); // If got here because the resource with same path has different type, replace it.
+ } else {
+ r->set_path_cache(path);
+ }
}
r->set_scene_unique_id(id);
}
@@ -1187,7 +1189,22 @@ Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const Strin
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'.");
ResourceLoaderBinary loader;
- loader.cache_mode = p_cache_mode;
+ switch (p_cache_mode) {
+ case CACHE_MODE_IGNORE:
+ case CACHE_MODE_REUSE:
+ case CACHE_MODE_REPLACE:
+ loader.cache_mode = p_cache_mode;
+ loader.cache_mode_for_external = CACHE_MODE_REUSE;
+ break;
+ case CACHE_MODE_IGNORE_DEEP:
+ loader.cache_mode = CACHE_MODE_IGNORE;
+ loader.cache_mode_for_external = p_cache_mode;
+ break;
+ case CACHE_MODE_REPLACE_DEEP:
+ loader.cache_mode = CACHE_MODE_REPLACE;
+ loader.cache_mode_for_external = p_cache_mode;
+ break;
+ }
loader.use_sub_threads = p_use_sub_threads;
loader.progress = r_progress;
String path = !p_original_path.is_empty() ? p_original_path : p_path;
diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h
index e64485d404..e01c5fa467 100644
--- a/core/io/resource_format_binary.h
+++ b/core/io/resource_format_binary.h
@@ -85,6 +85,7 @@ class ResourceLoaderBinary {
Error error = OK;
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
+ ResourceFormatLoader::CacheMode cache_mode_for_external = ResourceFormatLoader::CACHE_MODE_REUSE;
friend class ResourceFormatLoaderBinary;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index a3fc7bc370..ff563a35b2 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -188,6 +188,8 @@ void ResourceFormatLoader::_bind_methods() {
BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE);
BIND_ENUM_CONSTANT(CACHE_MODE_REUSE);
BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE);
+ BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE_DEEP);
+ BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE_DEEP);
GDVIRTUAL_BIND(_get_recognized_extensions);
GDVIRTUAL_BIND(_recognize_path, "path", "type");
@@ -339,9 +341,11 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
load_task.cond_var = nullptr;
}
+ bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP;
+ bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP;
if (load_task.resource.is_valid()) {
- if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- if (load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE) {
+ if (!ignoring) {
+ if (replacing) {
Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path);
if (old_res.is_valid() && old_res != load_task.resource) {
// If resource is already loaded, only replace its data, to avoid existing invalidating instances.
@@ -349,8 +353,8 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
load_task.resource = old_res;
}
}
- load_task.resource->set_path(load_task.local_path, load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE);
- } else if (!load_task.local_path.is_resource_file()) {
+ load_task.resource->set_path(load_task.local_path, replacing);
+ } else {
load_task.resource->set_path_cache(load_task.local_path);
}
@@ -370,7 +374,7 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
if (_loaded_callback) {
_loaded_callback(load_task.resource, load_task.local_path);
}
- } else if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
+ } else if (!ignoring) {
Ref<Resource> existing = ResourceCache::get_ref(load_task.local_path);
if (existing.is_valid()) {
load_task.resource = existing;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 1f79b83f11..5caf699d32 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -47,9 +47,11 @@ class ResourceFormatLoader : public RefCounted {
public:
enum CacheMode {
- CACHE_MODE_IGNORE, // Resource and subresources do not use path cache, no path is set into resource.
- CACHE_MODE_REUSE, // Resource and subresources use patch cache, reuse existing loaded resources instead of loading from disk when available.
- CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk.
+ CACHE_MODE_IGNORE,
+ CACHE_MODE_REUSE,
+ CACHE_MODE_REPLACE,
+ CACHE_MODE_IGNORE_DEEP,
+ CACHE_MODE_REPLACE_DEEP,
};
protected:
diff --git a/core/math/geometry_3d.cpp b/core/math/geometry_3d.cpp
index e2edf8b23e..4d55455166 100644
--- a/core/math/geometry_3d.cpp
+++ b/core/math/geometry_3d.cpp
@@ -393,7 +393,7 @@ static inline void _build_faces(uint8_t ***p_cell_status, int x, int y, int z, i
return;
}
-#define vert(m_idx) Vector3(((m_idx)&4) >> 2, ((m_idx)&2) >> 1, (m_idx)&1)
+#define vert(m_idx) Vector3(((m_idx) & 4) >> 2, ((m_idx) & 2) >> 1, (m_idx) & 1)
static const uint8_t indices[6][4] = {
{ 7, 6, 4, 5 },
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 63adc5b502..231a8e4d68 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -599,7 +599,9 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class)
placeholder_extension->property_can_revert = &PlaceholderExtensionInstance::placeholder_instance_property_can_revert;
placeholder_extension->property_get_revert = &PlaceholderExtensionInstance::placeholder_instance_property_get_revert;
placeholder_extension->validate_property = &PlaceholderExtensionInstance::placeholder_instance_validate_property;
+#ifndef DISABLE_DEPRECATED
placeholder_extension->notification = nullptr;
+#endif // DISABLE_DEPRECATED
placeholder_extension->notification2 = &PlaceholderExtensionInstance::placeholder_instance_notification;
placeholder_extension->to_string = &PlaceholderExtensionInstance::placeholder_instance_to_string;
placeholder_extension->reference = &PlaceholderExtensionInstance::placeholder_instance_reference;
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index a1723adb9a..88b867a1ca 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -226,7 +226,7 @@ class MethodBindVarArgT : public MethodBindVarArgBase<MethodBindVarArgT<T>, T, v
public:
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == MethodBind::get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
(static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>::method)(p_args, p_arg_count, r_error);
return {};
@@ -265,7 +265,7 @@ public:
#endif
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == MethodBind::get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
return (static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>::method)(p_args, p_arg_count, r_error);
}
@@ -336,7 +336,7 @@ public:
#endif
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_variant_args_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
@@ -348,7 +348,7 @@ public:
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_validated_object_instance_args(static_cast<T *>(p_object), method, p_args);
@@ -359,7 +359,7 @@ public:
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_ptr_args<T, P...>(static_cast<T *>(p_object), method, p_args);
@@ -420,7 +420,7 @@ public:
#endif
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_variant_argsc_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
@@ -432,7 +432,7 @@ public:
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_validated_object_instance_argsc(static_cast<T *>(p_object), method, p_args);
@@ -443,7 +443,7 @@ public:
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_ptr_argsc<T, P...>(static_cast<T *>(p_object), method, p_args);
@@ -515,7 +515,7 @@ public:
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
Variant ret;
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_variant_args_ret_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
@@ -527,7 +527,7 @@ public:
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_validated_object_instance_args_ret(static_cast<T *>(p_object), method, p_args, r_ret);
@@ -538,7 +538,7 @@ public:
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_ptr_args_ret<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
@@ -611,7 +611,7 @@ public:
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
Variant ret;
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_variant_args_retc_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
@@ -623,7 +623,7 @@ public:
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_validated_object_instance_args_retc(static_cast<T *>(p_object), method, p_args, r_ret);
@@ -634,7 +634,7 @@ public:
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TOOLS_ENABLED
- ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
+ ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
#endif
#ifdef TYPED_METHOD_BIND
call_with_ptr_args_retc<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 5d5a721811..b34182cdaa 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -1483,10 +1483,15 @@ String Object::tr(const StringName &p_message, const StringName &p_context) cons
}
if (Engine::get_singleton()->is_editor_hint()) {
+ String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context);
+ if (!tr_msg.is_empty()) {
+ return tr_msg;
+ }
+
return TranslationServer::get_singleton()->tool_translate(p_message, p_context);
- } else {
- return TranslationServer::get_singleton()->translate(p_message, p_context);
}
+
+ return TranslationServer::get_singleton()->translate(p_message, p_context);
}
String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -1499,10 +1504,15 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu
}
if (Engine::get_singleton()->is_editor_hint()) {
+ String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context);
+ if (!tr_msg.is_empty()) {
+ return tr_msg;
+ }
+
return TranslationServer::get_singleton()->tool_translate_plural(p_message, p_message_plural, p_n, p_context);
- } else {
- return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context);
}
+
+ return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context);
}
void Object::_clear_internal_resource_paths(const Variant &p_var) {
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index 32e4564c5e..8ae2efb787 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -92,6 +92,14 @@ StringName NodePath::get_subname(int p_idx) const {
return data->subpath[p_idx];
}
+int NodePath::get_total_name_count() const {
+ if (!data) {
+ return 0;
+ }
+
+ return data->path.size() + data->subpath.size();
+}
+
void NodePath::unref() {
if (data && data->refcount.unref()) {
memdelete(data);
@@ -229,6 +237,27 @@ StringName NodePath::get_concatenated_subnames() const {
return data->concatenated_subpath;
}
+NodePath NodePath::slice(int p_begin, int p_end) const {
+ const int name_count = get_name_count();
+ const int total_count = get_total_name_count();
+
+ int begin = CLAMP(p_begin, -total_count, total_count);
+ if (begin < 0) {
+ begin += total_count;
+ }
+ int end = CLAMP(p_end, -total_count, total_count);
+ if (end < 0) {
+ end += total_count;
+ }
+ const int sub_begin = MAX(begin - name_count - 1, 0);
+ const int sub_end = MAX(end - name_count, 0);
+
+ const Vector<StringName> names = get_names().slice(begin, end);
+ const Vector<StringName> sub_names = get_subnames().slice(sub_begin, sub_end);
+ const bool absolute = is_absolute() && (begin == 0);
+ return NodePath(names, sub_names, absolute);
+}
+
NodePath NodePath::rel_path_to(const NodePath &p_np) const {
ERR_FAIL_COND_V(!is_absolute(), NodePath());
ERR_FAIL_COND_V(!p_np.is_absolute(), NodePath());
@@ -331,7 +360,7 @@ NodePath NodePath::simplified() const {
}
NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) {
- if (p_path.size() == 0) {
+ if (p_path.size() == 0 && !p_absolute) {
return;
}
@@ -343,7 +372,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) {
}
NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) {
- if (p_path.size() == 0 && p_subpath.size() == 0) {
+ if (p_path.size() == 0 && p_subpath.size() == 0 && !p_absolute) {
return;
}
diff --git a/core/string/node_path.h b/core/string/node_path.h
index 876d69924e..56799839d7 100644
--- a/core/string/node_path.h
+++ b/core/string/node_path.h
@@ -57,10 +57,12 @@ public:
StringName get_name(int p_idx) const;
int get_subname_count() const;
StringName get_subname(int p_idx) const;
+ int get_total_name_count() const;
Vector<StringName> get_names() const;
Vector<StringName> get_subnames() const;
StringName get_concatenated_names() const;
StringName get_concatenated_subnames() const;
+ NodePath slice(int p_begin, int p_end = INT_MAX) const;
NodePath rel_path_to(const NodePath &p_np) const;
NodePath get_as_property_path() const;
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index db0c3f6006..2f25f56eac 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -772,6 +772,20 @@ StringName TranslationServer::tool_translate_plural(const StringName &p_message,
return p_message_plural;
}
+void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
+ property_translation = p_translation;
+}
+
+StringName TranslationServer::property_translate(const StringName &p_message) const {
+ if (property_translation.is_valid()) {
+ StringName r = property_translation->get_message(p_message);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
doc_translation = p_translation;
}
@@ -800,13 +814,13 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
return p_message_plural;
}
-void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
- property_translation = p_translation;
+void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
+ extractable_translation = p_translation;
}
-StringName TranslationServer::property_translate(const StringName &p_message) const {
- if (property_translation.is_valid()) {
- StringName r = property_translation->get_message(p_message);
+StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_message(p_message, p_context);
if (r) {
return r;
}
@@ -814,6 +828,20 @@ StringName TranslationServer::property_translate(const StringName &p_message) co
return p_message;
}
+StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
bool TranslationServer::is_pseudolocalization_enabled() const {
return pseudolocalization_enabled;
}
diff --git a/core/string/translation.h b/core/string/translation.h
index 4eca37f6be..bd7082dc9d 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -82,8 +82,9 @@ class TranslationServer : public Object {
HashSet<Ref<Translation>> translations;
Ref<Translation> tool_translation;
- Ref<Translation> doc_translation;
Ref<Translation> property_translation;
+ Ref<Translation> doc_translation;
+ Ref<Translation> extractable_translation;
bool enabled = true;
@@ -181,11 +182,14 @@ public:
Ref<Translation> get_tool_translation() const;
StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_property_translation(const Ref<Translation> &p_translation);
+ StringName property_translate(const StringName &p_message) const;
void set_doc_translation(const Ref<Translation> &p_translation);
StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_property_translation(const Ref<Translation> &p_translation);
- StringName property_translate(const StringName &p_message) const;
+ void set_extractable_translation(const Ref<Translation> &p_translation);
+ StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
void setup();
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index a8a96e6e3f..f4b00255a1 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -5391,9 +5391,7 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
/**
* "Run-time TRanslate". Performs string replacement for internationalization
- * within a running project. The translation string must be supplied by the
- * project, as Godot does not provide built-in translations for `RTR()` strings
- * to keep binary size low. A translation context can optionally be specified to
+ * without the editor. A translation context can optionally be specified to
* disambiguate between identical source strings in translations. When
* placeholders are desired, use `vformat(RTR("Example: %s"), some_string)`.
* If a string mentions a quantity (and may therefore need a dynamic plural form),
@@ -5407,9 +5405,8 @@ String RTR(const String &p_text, const String &p_context) {
String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context);
if (rtr.is_empty() || rtr == p_text) {
return TranslationServer::get_singleton()->translate(p_text, p_context);
- } else {
- return rtr;
}
+ return rtr;
}
return p_text;
@@ -5417,13 +5414,10 @@ String RTR(const String &p_text, const String &p_context) {
/**
* "Run-time TRanslate for N items". Performs string replacement for
- * internationalization within a running project. The translation string must be
- * supplied by the project, as Godot does not provide built-in translations for
- * `RTRN()` strings to keep binary size low. A translation context can
- * optionally be specified to disambiguate between identical source strings in
- * translations. Use `RTR()` if the string doesn't need dynamic plural form.
- * When placeholders are desired, use
- * `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`.
+ * internationalization without the editor. A translation context can optionally
+ * be specified to disambiguate between identical source strings in translations.
+ * Use `RTR()` if the string doesn't need dynamic plural form. When placeholders
+ * are desired, use `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`.
* The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
*
* NOTE: Do not use `RTRN()` in editor-only code (typically within the `editor/`
@@ -5434,9 +5428,8 @@ String RTRN(const String &p_text, const String &p_text_plural, int p_n, const St
String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
if (rtr.is_empty() || rtr == p_text || rtr == p_text_plural) {
return TranslationServer::get_singleton()->translate_plural(p_text, p_text_plural, p_n, p_context);
- } else {
- return rtr;
}
+ return rtr;
}
// Return message based on English plural rule if translation is not possible.
diff --git a/core/string/ustring.h b/core/string/ustring.h
index b1348ceb48..468a015302 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -556,6 +556,43 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
String RTR(const String &p_text, const String &p_context = "");
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
+/**
+ * "Extractable TRanslate". Used for strings that can appear inside an exported
+ * project (such as the ones in nodes like `FileDialog`), which are made possible
+ * to add in the POT generator. A translation context can optionally be specified
+ * to disambiguate between identical source strings in translations.
+ * When placeholders are desired, use vformat(ETR("Example: %s"), some_string)`.
+ * If a string mentions a quantity (and may therefore need a dynamic plural form),
+ * use `ETRN()` instead of `ETR()`.
+ *
+ * NOTE: This function is for string extraction only, and will just return the
+ * string it was given. The translation itself should be done internally by nodes
+ * with `atr()` instead.
+ */
+_FORCE_INLINE_ String ETR(const String &p_text, const String &p_context = "") {
+ return p_text;
+}
+
+/**
+ * "Extractable TRanslate for N items". Used for strings that can appear inside an
+ * exported project (such as the ones in nodes like `FileDialog`), which are made
+ * possible to add in the POT generator. A translation context can optionally be
+ * specified to disambiguate between identical source strings in translations.
+ * Use `ETR()` if the string doesn't need dynamic plural form. When placeholders
+ * are desired, use `vformat(ETRN("%d item", "%d items", some_integer), some_integer)`.
+ * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
+ *
+ * NOTE: This function is for string extraction only, and will just return the
+ * string it was given. The translation itself should be done internally by nodes
+ * with `atr()` instead.
+ */
+_FORCE_INLINE_ String ETRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "") {
+ if (p_n == 1) {
+ return p_text;
+ }
+ return p_text_plural;
+}
+
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);
_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) {
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 55f687bdf9..6bad6f5a5b 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -30,10 +30,11 @@
#include "callable.h"
-#include "callable_bind.h"
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
+#include "core/variant/callable_bind.h"
+#include "core/variant/variant_callable.h"
void Callable::call_deferredp(const Variant **p_arguments, int p_argcount) const {
MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount, true);
@@ -327,14 +328,27 @@ Callable::operator String() const {
}
}
+Callable Callable::create(const Variant &p_variant, const StringName &p_method) {
+ ERR_FAIL_COND_V_MSG(p_method == StringName(), Callable(), "Method argument to Callable::create method must be a non-empty string.");
+
+ switch (p_variant.get_type()) {
+ case Variant::NIL:
+ return Callable(ObjectID(), p_method);
+ case Variant::OBJECT:
+ return Callable(p_variant.operator ObjectID(), p_method);
+ default:
+ return Callable(memnew(VariantCallable(p_variant, p_method)));
+ }
+}
+
Callable::Callable(const Object *p_object, const StringName &p_method) {
- if (p_method == StringName()) {
+ if (unlikely(p_method == StringName())) {
object = 0;
- ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string");
+ ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string.");
}
- if (p_object == nullptr) {
+ if (unlikely(p_object == nullptr)) {
object = 0;
- ERR_FAIL_MSG("Object argument to Callable constructor must be non-null");
+ ERR_FAIL_MSG("Object argument to Callable constructor must be non-null.");
}
object = p_object->get_instance_id();
@@ -342,9 +356,9 @@ Callable::Callable(const Object *p_object, const StringName &p_method) {
}
Callable::Callable(ObjectID p_object, const StringName &p_method) {
- if (p_method == StringName()) {
+ if (unlikely(p_method == StringName())) {
object = 0;
- ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string");
+ ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string.");
}
object = p_object;
@@ -352,9 +366,9 @@ Callable::Callable(ObjectID p_object, const StringName &p_method) {
}
Callable::Callable(CallableCustom *p_custom) {
- if (p_custom->referenced) {
+ if (unlikely(p_custom->referenced)) {
object = 0;
- ERR_FAIL_MSG("Callable custom is already referenced");
+ ERR_FAIL_MSG("Callable custom is already referenced.");
}
p_custom->referenced = true;
object = 0; //ensure object is all zero, since pointer may be 32 bits
diff --git a/core/variant/callable.h b/core/variant/callable.h
index 38872b71ef..bba69d453e 100644
--- a/core/variant/callable.h
+++ b/core/variant/callable.h
@@ -125,6 +125,8 @@ public:
operator String() const;
+ static Callable create(const Variant &p_variant, const StringName &p_method);
+
Callable(const Object *p_object, const StringName &p_method);
Callable(ObjectID p_object, const StringName &p_method);
Callable(CallableCustom *p_custom);
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index b551a7059e..543ee1135f 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2033,11 +2033,13 @@ static void _register_variant_builtin_methods() {
bind_method(NodePath, get_subname, sarray("idx"), varray());
bind_method(NodePath, get_concatenated_names, sarray(), varray());
bind_method(NodePath, get_concatenated_subnames, sarray(), varray());
+ bind_method(NodePath, slice, sarray("begin", "end"), varray(INT_MAX));
bind_method(NodePath, get_as_property_path, sarray(), varray());
bind_method(NodePath, is_empty, sarray(), varray());
/* Callable */
+ bind_static_method(Callable, create, sarray("variant", "method"), varray());
bind_method(Callable, callv, sarray("arguments"), varray());
bind_method(Callable, is_null, sarray(), varray());
bind_method(Callable, is_custom, sarray(), varray());
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 171074188f..79bed9be33 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -810,7 +810,7 @@ struct VariantInternalAccessor<bool> {
#define VARIANT_ACCESSOR_NUMBER(m_type) \
template <> \
struct VariantInternalAccessor<m_type> { \
- static _FORCE_INLINE_ m_type get(const Variant *v) { return (m_type)*VariantInternal::get_int(v); } \
+ static _FORCE_INLINE_ m_type get(const Variant *v) { return (m_type) * VariantInternal::get_int(v); } \
static _FORCE_INLINE_ void set(Variant *v, m_type p_value) { *VariantInternal::get_int(v) = p_value; } \
};
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 50c9c10987..20941b944f 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -433,9 +433,9 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
} \
m_assign_type num; \
if (value->get_type() == Variant::INT) { \
- num = (m_assign_type)*VariantGetInternalPtr<int64_t>::get_ptr(value); \
+ num = (m_assign_type) * VariantGetInternalPtr<int64_t>::get_ptr(value); \
} else if (value->get_type() == Variant::FLOAT) { \
- num = (m_assign_type)*VariantGetInternalPtr<double>::get_ptr(value); \
+ num = (m_assign_type) * VariantGetInternalPtr<double>::get_ptr(value); \
} else { \
*oob = false; \
*valid = false; \
@@ -495,9 +495,9 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
} \
m_assign_type num; \
if (value->get_type() == Variant::INT) { \
- num = (m_assign_type)*VariantGetInternalPtr<int64_t>::get_ptr(value); \
+ num = (m_assign_type) * VariantGetInternalPtr<int64_t>::get_ptr(value); \
} else if (value->get_type() == Variant::FLOAT) { \
- num = (m_assign_type)*VariantGetInternalPtr<double>::get_ptr(value); \
+ num = (m_assign_type) * VariantGetInternalPtr<double>::get_ptr(value); \
} else { \
*oob = false; \
*valid = false; \
diff --git a/core/version.h b/core/version.h
index abb81312ac..18a97cadf0 100644
--- a/core/version.h
+++ b/core/version.h
@@ -33,6 +33,8 @@
#include "core/version_generated.gen.h"
+#include <stdint.h>
+
// Copied from typedefs.h to stay lean.
#ifndef _STR
#define _STR(m_x) #m_x
@@ -77,4 +79,8 @@
// Git commit hash, generated at build time in `core/version_hash.gen.cpp`.
extern const char *const VERSION_HASH;
+// Git commit date UNIX timestamp (in seconds), generated at build time in `core/version_hash.gen.cpp`.
+// Set to 0 if unknown.
+extern const uint64_t VERSION_TIMESTAMP;
+
#endif // VERSION_H