From 02476f5a3eb2c180343691143bb109fed4f038ed Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Wed, 30 Apr 2025 14:21:46 -0500 Subject: [PATCH] Core: Implement `NotNull` template --- core/extension/extension_api_dump.cpp | 2 +- core/extension/gdextension_interface.cpp | 2 +- core/object/class_db.cpp | 3 +- core/object/class_db.h | 2 +- core/templates/not_null.h | 523 +++++++++++++++++++++++ core/variant/binder_common.h | 7 + core/variant/method_ptrcall.h | 11 + core/variant/type_info.h | 10 + core/variant/variant.h | 1 + core/variant/variant_internal.h | 6 + scene/main/node.cpp | 2 +- scene/main/node.h | 2 +- 12 files changed, 564 insertions(+), 7 deletions(-) create mode 100644 core/templates/not_null.h diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 93b1b27207c8..c44956ed5b36 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -88,7 +88,7 @@ static String get_property_info_type_name(const PropertyInfo &p_info) { } static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) { - static const char *argmeta[13] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32" }; + static const char *argmeta[14] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32", "not_null" }; return argmeta[metadata]; } diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 725475d9a7a6..bcad57aca70f 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1359,7 +1359,7 @@ static void gdextension_object_free_instance_binding(GDExtensionObjectPtr p_obje static void gdextension_object_set_instance(GDExtensionObjectPtr p_object, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance) { const StringName classname = *reinterpret_cast(p_classname); - Object *o = (Object *)p_object; + NeverNull o = NeverNull((Object *)p_object); ClassDB::set_object_extension_instance(o, classname, p_instance); } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 9e2ee8a2a83f..16e58a3d9586 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -730,8 +730,7 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) } #endif -void ClassDB::set_object_extension_instance(Object *p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance) { - ERR_FAIL_NULL(p_object); +void ClassDB::set_object_extension_instance(NeverNull p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance) { ClassInfo *ti; { Locker::Lock lock(Locker::STATE_READ); diff --git a/core/object/class_db.h b/core/object/class_db.h index f90b691b0cb7..2148f9b6876b 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -325,7 +325,7 @@ class ClassDB { static Object *instantiate(const StringName &p_class); static Object *instantiate_no_placeholders(const StringName &p_class); static Object *instantiate_without_postinitialization(const StringName &p_class); - static void set_object_extension_instance(Object *p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance); + static void set_object_extension_instance(NeverNull p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance); static APIType get_api_type(const StringName &p_class); diff --git a/core/templates/not_null.h b/core/templates/not_null.h new file mode 100644 index 000000000000..a759eb163351 --- /dev/null +++ b/core/templates/not_null.h @@ -0,0 +1,523 @@ +/**************************************************************************/ +/* not_null.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" + +// Implementation derived heavily from gsl-lite: +// https://github.com/gsl-lite/gsl-lite + +template +class NotNull; + +template +using NeverNull = NotNull; + +namespace Internal { + +// Helper to figure out whether a pointer has an element type. +// Avoid SFINAE for unary `operator*` (doesn't work for `std::unique_ptr<>` and the like) if an `element_type` member exists. +template +struct has_element_type_ : std::false_type {}; +template +struct has_element_type_())>> : std::true_type {}; +template +struct has_element_type : has_element_type_ {}; +template +struct has_element_type> : std::true_type {}; + +template +inline constexpr bool has_element_type_v = has_element_type::value; + +// Helper to figure out the pointed-to type of a pointer. +template +struct element_type_helper { + // For types without a member element_type (this could handle typed raw pointers but not `void*`) + using type = std::remove_reference_t())>; +}; +template +struct element_type_helper> { + // For types with a member element_type + using type = typename T::element_type; +}; +template +struct element_type_helper { + using type = T; +}; + +template +using element_type_helper_t = typename element_type_helper::type; + +template +struct is_not_null_or_bool_oracle : std::false_type {}; +template +struct is_not_null_or_bool_oracle> : std::true_type {}; +template <> +struct is_not_null_or_bool_oracle : std::true_type {}; + +template +inline constexpr bool is_not_null_or_bool_oracle_v = is_not_null_or_bool_oracle::value; + +template +struct not_null_data; + +template +struct not_null_data { + T _ptr; + + constexpr not_null_data(T &&p_ptr) : + _ptr(std::move(p_ptr)) {} + + constexpr not_null_data(not_null_data &&p_other) : + _ptr(std::move(p_other._ptr)) { + if constexpr (Fatal) { + CRASH_COND(_ptr == nullptr); + } else { + ERR_FAIL_NULL(_ptr); + } + } + constexpr not_null_data &operator=(not_null_data &&p_other) { + if (unlikely(&p_other == this)) { + return *this; + } + if constexpr (Fatal) { + CRASH_COND(p_other._ptr == nullptr); + } else { + ERR_FAIL_NULL_V(p_other._ptr, *this); + } + _ptr = std::move(p_other._ptr); + return *this; + } + + not_null_data(const not_null_data &) = delete; + not_null_data &operator=(const not_null_data &) = delete; +}; + +template +struct not_null_data { + T _ptr; + + constexpr not_null_data(const T &p_ptr) : + _ptr(p_ptr) {} + + constexpr not_null_data(T &&p_ptr) : + _ptr(std::move(p_ptr)) {} + + constexpr not_null_data(not_null_data &&p_other) : + _ptr(std::move(p_other._ptr)) { + if constexpr (Fatal) { + CRASH_COND(_ptr == nullptr); + } else { + ERR_FAIL_NULL(_ptr); + } + } + constexpr not_null_data &operator=(not_null_data &&p_other) { + if (unlikely(&p_other == this)) { + return *this; + } + if constexpr (Fatal) { + CRASH_COND(p_other._ptr == nullptr); + } else { + ERR_FAIL_NULL_V(p_other._ptr, *this); + } + _ptr = std::move(p_other._ptr); + return *this; + } + + constexpr not_null_data(const not_null_data &p_other) : + _ptr(p_other._ptr) { + if constexpr (Fatal) { + CRASH_COND(_ptr == nullptr); + } else { + ERR_FAIL_NULL(_ptr); + } + } + constexpr not_null_data &operator=(const not_null_data &p_other) { + if constexpr (Fatal) { + CRASH_COND(p_other._ptr == nullptr); + } else { + ERR_FAIL_NULL_V(p_other._ptr, *this); + } + _ptr = p_other._ptr; + return *this; + } +}; + +template +struct not_null_data { + T *_ptr; + + constexpr not_null_data(T *p_ptr) : + _ptr(p_ptr) {} +}; + +template +struct is_copyable : std::bool_constant && std::is_copy_assignable_v> {}; + +template +inline constexpr bool is_copyable_v = is_copyable::value; + +template +struct not_null_accessor; + +template +struct not_null_elem { + using element_type = element_type_helper_t; + + [[nodiscard]] constexpr element_type *get() const { + return not_null_accessor::get_checked(static_cast(*this)).get(); + } +}; +template +struct not_null_elem {}; + +template +struct not_null_deref : not_null_elem> { + using element_type = element_type_helper_t; + + [[nodiscard]] constexpr element_type &operator*() const { + return *not_null_accessor::get_checked(static_cast(*this)); + } +}; + +template +struct not_null_deref : not_null_elem> {}; + +template +struct is_void_ptr : std::is_void> {}; + +template +struct is_dereferencable : std::conjunction, std::negation>> {}; + +template +inline constexpr bool is_dereferencable_v = is_dereferencable::value; + +} // namespace Internal + +template +struct is_nullable : std::is_assignable &, std::nullptr_t> {}; + +template +inline constexpr bool is_nullable_v = is_nullable::value; + +template +class NotNull : public Internal::not_null_deref, T, Fatal, Internal::is_dereferencable_v> { + static_assert(!std::is_reference_v, "T may not be a reference type"); + static_assert(!std::is_const_v && !std::is_volatile_v, "T may not be cv-qualified"); + static_assert(is_nullable_v, "T must be a nullable type"); + + Internal::not_null_data> _data; + + // need to access `NotNull::_data` + template + friend struct Internal::not_null_accessor; + + using accessor = Internal::not_null_accessor; + +public: + // Cannot be constructed as empty, nor via `nullptr`. + NotNull() = delete; + NotNull(std::nullptr_t) = delete; + + // `NotNull` of same type. + constexpr NotNull(const NotNull &p_other) = default; + constexpr NotNull(NotNull &&p_other) = default; + constexpr NotNull &operator=(const NotNull &p_other) = default; + constexpr NotNull &operator=(NotNull &&p_other) = default; + + // `NotNull` of different type. + template && !std::is_convertible_v, int> = 0> + constexpr explicit NotNull(NotNull p_other) : + _data(T(Internal::not_null_accessor::get_checked(std::move(p_other)))) {} + + template , int> = 0> + constexpr NotNull(NotNull p_other) : + _data(T(Internal::not_null_accessor::get_checked(std::move(p_other)))) {} + + // Fatal constructors (explicit). + template && is_nullable_v, int> = 0> + constexpr explicit NotNull(U p_other) : + _data(T(std::move(p_other))) { + CRASH_COND(_data._ptr == nullptr); + } + template && std::is_function_v, int> = 0> + constexpr NotNull(const U &p_other) : + _data(T(p_other)) {} + + template && !std::is_function_v && !is_nullable_v, int> = 0> + constexpr NotNull(U p_other) : + _data(T(std::move(p_other))) { + CRASH_COND(_data._ptr == nullptr); + } + + // Nonfatal constructors (implicit). + template && !std::is_convertible_v, int> = 0> + constexpr explicit NotNull(U other) : + _data(T(std::move(other))) { + ERR_FAIL_NULL(_data._ptr); + } + template , int> = 0> + constexpr NotNull(U other) : + _data(std::move(other)) { + ERR_FAIL_NULL(_data._ptr); + } + + // Conversion operators from explicitly convertible types. + template && !std::is_convertible_v && !Internal::is_not_null_or_bool_oracle_v, int> = 0> + [[nodiscard]] constexpr explicit operator U() const & { + return U(accessor::get_checked(*this)); + } + template && !std::is_convertible_v && !Internal::is_not_null_or_bool_oracle_v, int> = 0> + [[nodiscard]] constexpr explicit operator U() && { + return U(accessor::get_checked(std::move(*this))); + } + + // Conversion operators from implicitly convertible types. + template && std::is_convertible_v && !Internal::is_not_null_or_bool_oracle_v, int> = 0> + [[nodiscard]] constexpr operator U() const & { + return accessor::get_checked(*this); + } + template && !Internal::is_not_null_or_bool_oracle_v, int> = 0> + [[nodiscard]] constexpr operator U() && { + return accessor::get_checked(std::move(*this)); + } + + [[nodiscard]] constexpr const T &operator->() const { + return accessor::get_checked(*this); + } + + template + constexpr auto operator()(Ts &&...args) const + -> decltype(_data._ptr(std::forward(args)...)) { + return accessor::get_checked(*this)(std::forward(args)...); + } + + // Comparison operators. + template + [[nodiscard]] inline constexpr bool operator==(const NotNull &p_right) { return (*this).operator->() == p_right.operator->(); } + template + [[nodiscard]] inline constexpr bool operator==(const U &p_right) { return (*this).operator->() == p_right; } + + template + [[nodiscard]] inline constexpr bool operator!=(const NotNull &p_right) { return !(*this == p_right); } + template + [[nodiscard]] inline constexpr bool operator!=(const U &p_right) { return !(*this == p_right); } + + template + [[nodiscard]] inline constexpr bool operator<(const NotNull &p_right) { return (*this).operator->() < p_right.operator->(); } + template + [[nodiscard]] inline constexpr bool operator<(const U &p_right) { return (*this).operator->() < p_right; } + + template + [[nodiscard]] inline constexpr bool operator<=(const NotNull &p_right) { return !(p_right < *this); } + template + [[nodiscard]] inline constexpr bool operator<=(const U &p_right) { return !(p_right < *this); } + + template + [[nodiscard]] inline constexpr bool operator>(const NotNull &p_right) { return p_right < *this; } + template + [[nodiscard]] inline constexpr bool operator>(const U &p_right) { return p_right < *this; } + + template + [[nodiscard]] inline constexpr bool operator>=(const NotNull &p_right) { return !(*this < p_right); } + template + [[nodiscard]] inline constexpr bool operator>=(const U &p_right) { return !(*this < p_right); } + + // Operators we never want. + NotNull &operator=(std::nullptr_t) = delete; + NotNull &operator++() = delete; + NotNull &operator--() = delete; + NotNull operator++(int) = delete; + NotNull operator--(int) = delete; + NotNull operator+(size_t) = delete; + NotNull &operator+=(size_t) = delete; + NotNull operator-(size_t) = delete; + NotNull &operator-=(size_t) = delete; + NotNull operator+(std::ptrdiff_t) = delete; + NotNull &operator+=(std::ptrdiff_t) = delete; + NotNull operator=(std::ptrdiff_t) = delete; + NotNull &operator-=(std::ptrdiff_t) = delete; + void operator[](std::ptrdiff_t) const = delete; + template + std::ptrdiff_t operator+(const NotNull &) = delete; + template + std::ptrdiff_t operator-(const NotNull &) = delete; +}; + +// template +// NotNull(U) -> NotNull; +// template +// NotNull(NotNull) -> NotNull; + +void make_not_null(std::nullptr_t) = delete; + +template +[[nodiscard]] constexpr NotNull make_not_null(U u) { + return NotNull(std::move(u)); +} +template +[[nodiscard]] constexpr NotNull make_not_null(NotNull u) { + return std::move(u); +} + +namespace Internal { + +template +struct as_nullable_helper { + using type = std::remove_reference_t>; +}; +template +struct as_nullable_helper> {}; + +template +using as_nullable_helper_t = typename as_nullable_helper::type; + +template +struct not_null_accessor { + template + static T get(NotNull &&p_value) { + return std::move(p_value._data._ptr); + } + template + static T get_checked(NotNull &&p_value) { + if constexpr (Fatal) { + CRASH_COND(p_value._data._ptr == nullptr); + } else { + ERR_FAIL_NULL_V(p_value._data._ptr, std::move(p_value._data._ptr)); + } + return std::move(p_value._data._ptr); + } + template + static const T &get(const NotNull &p_value) { + return p_value._data._ptr; + } + template + static bool is_valid(const NotNull &p_value) { + return p_value._data._ptr != nullptr; + } + template + static void check(const NotNull &p_value) { + if constexpr (Fatal) { + CRASH_COND(p_value._data._ptr == nullptr); + } else { + ERR_FAIL_NULL(p_value._data._ptr); + } + } + template + static const T &get_checked(const NotNull &p_value) { + if constexpr (Fatal) { + CRASH_COND(p_value._data._ptr == nullptr); + } else { + ERR_FAIL_NULL_V(p_value._data._ptr, p_value._data._ptr); + } + return p_value._data._ptr; + } +}; +template +struct not_null_accessor { + template + static T *const &get(const NotNull &p_value) { return p_value._data._ptr; } + template + static bool is_valid(const NotNull & /*p_value*/) { return true; } + template + static void checkconst(NotNull & /*p_value*/) {} + template + static T *const &get_checked(const NotNull &p_value) { return p_value._data._ptr; } +}; + +namespace NoADL { + +template +[[nodiscard]] constexpr as_nullable_helper_t as_nullable(T &&p_value) { + return std::move(p_value); +} + +template +[[nodiscard]] constexpr T as_nullable(NotNull &&p_value) { + return not_null_accessor::get_checked(std::move(p_value)); +} + +template +[[nodiscard]] constexpr const T &as_nullable(const NotNull &p_value) { + return not_null_accessor::get_checked(p_value); +} + +template +[[nodiscard]] constexpr bool is_valid(const NotNull &p_value) { + return not_null_accessor::is_valid(p_value); +} + +} // namespace NoADL +} // namespace Internal + +using namespace Internal::NoADL; + +// Unwanted operators. + +template +NotNull operator+(std::ptrdiff_t, const NotNull &) = delete; +template +NotNull operator-(std::ptrdiff_t, const NotNull &) = delete; +template +constexpr bool operator==(const NotNull &, std::nullptr_t) = delete; +template +constexpr bool operator==(std::nullptr_t, const NotNull &) = delete; +template +constexpr bool operator!=(const NotNull &, std::nullptr_t) = delete; +template +constexpr bool operator!=(std::nullptr_t, const NotNull &) = delete; + +// Right-hand operators. Convert to class methods in C++20. + +template +[[nodiscard]] inline constexpr bool operator==(const T &p_left, const NotNull &p_right) { + return p_left == p_right.operator->(); +} +template +[[nodiscard]] inline constexpr bool operator!=(const T &p_left, const NotNull &p_right) { + return p_left != p_right.operator->(); +} +template +[[nodiscard]] inline constexpr bool operator<(const T &p_left, const NotNull &p_right) { + return p_left < p_right.operator->(); +} +template +[[nodiscard]] inline constexpr bool operator<=(const T &p_left, const NotNull &p_right) { + return p_left <= p_right.operator->(); +} +template +[[nodiscard]] inline constexpr bool operator>(const T &p_left, const NotNull &p_right) { + return p_left > p_right.operator->(); +} +template +[[nodiscard]] inline constexpr bool operator>=(const T &p_left, const NotNull &p_right) { + return p_left >= p_right.operator->(); +} diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 67b68d30b608..491eda0d4c09 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -123,6 +123,13 @@ VARIANT_ENUM_CAST(Key); VARIANT_BITFIELD_CAST(KeyModifierMask); VARIANT_ENUM_CAST(KeyLocation); +template +struct VariantCaster> { + static _FORCE_INLINE_ NotNull cast(const Variant &p_variant) { + return NotNull(const_cast(Object::cast_to(p_variant))); + } +}; + template <> struct VariantCaster { static _FORCE_INLINE_ char32_t cast(const Variant &p_variant) { diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h index d7800dc94226..6bdc1703cf41 100644 --- a/core/variant/method_ptrcall.h +++ b/core/variant/method_ptrcall.h @@ -167,6 +167,17 @@ struct PtrToArg { } }; +template +struct PtrToArg> { + _FORCE_INLINE_ static NotNull convert(const void *p_ptr) { + return NotNull(*reinterpret_cast(p_ptr)); + } + typedef NotNull EncodeT; + _FORCE_INLINE_ static void encode(NotNull p_val, const void *p_ptr) { + *(const_cast *>(reinterpret_cast *>(p_ptr))) = p_val; + } +}; + // This is for ObjectID. template <> diff --git a/core/variant/type_info.h b/core/variant/type_info.h index b9d75823828f..4345c0f85936 100644 --- a/core/variant/type_info.h +++ b/core/variant/type_info.h @@ -50,6 +50,7 @@ enum Metadata { METADATA_REAL_IS_DOUBLE, METADATA_INT_IS_CHAR16, METADATA_INT_IS_CHAR32, + METADATA_OBJECT_IS_NOT_NULL, }; } @@ -180,6 +181,15 @@ struct GetTypeInfo>> { } }; +template +struct GetTypeInfo> { + static const Variant::Type VARIANT_TYPE = Variant::OBJECT; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_OBJECT_IS_NOT_NULL; + static inline PropertyInfo get_class_info() { + return PropertyInfo(StringName(std::remove_pointer_t::get_class_static())); + } +}; + namespace GodotTypeInfo { namespace Internal { inline String enum_qualified_name_to_class_info_name(const String &p_qualified_name) { diff --git a/core/variant/variant.h b/core/variant/variant.h index 9e1963bb2643..a1a5a7ff2704 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -56,6 +56,7 @@ #include "core/string/ustring.h" #include "core/templates/bit_field.h" #include "core/templates/list.h" +#include "core/templates/not_null.h" #include "core/templates/paged_allocator.h" #include "core/templates/rid.h" #include "core/variant/array.h" diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 48d13a5199f9..97b8b0d38ea3 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -1045,6 +1045,12 @@ struct VariantInternalAccessor { static _FORCE_INLINE_ void set(Variant *v, const Object *p_value) { VariantInternal::object_assign(v, p_value); } }; +template +struct VariantInternalAccessor> { + static _FORCE_INLINE_ NotNull get(const Variant *v) { return NotNull(const_cast(static_cast(*VariantInternal::get_object(v)))); } + static _FORCE_INLINE_ void set(Variant *v, const NotNull p_value) { VariantInternal::object_assign(v, p_value); } +}; + template <> struct VariantInternalAccessor { static _FORCE_INLINE_ Variant &get(Variant *v) { return *v; } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index a014f177cd18..3beda944f17c 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1807,7 +1807,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalM emit_signal(SNAME("child_order_changed")); } -void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_internal) { +void Node::add_child(NotNull p_child, bool p_force_readable_name, InternalMode p_internal) { ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Adding children to a node inside the SceneTree is only allowed from the main thread. Use call_deferred(\"add_child\",node)."); ERR_THREAD_GUARD diff --git a/scene/main/node.h b/scene/main/node.h index 54ef13ed4e2f..27f52e9c2a4d 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -477,7 +477,7 @@ class Node : public Object { InternalMode get_internal_mode() const; - void add_child(Node *p_child, bool p_force_readable_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED); + void add_child(NotNull p_child, bool p_force_readable_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED); void add_sibling(Node *p_sibling, bool p_force_readable_name = false); void remove_child(Node *p_child);