diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/doc_data.h | 1 | ||||
-rw-r--r-- | core/extension/native_extension.cpp | 2 | ||||
-rw-r--r-- | core/extension/native_extension.h | 2 | ||||
-rw-r--r-- | core/extension/native_extension_manager.cpp | 19 | ||||
-rw-r--r-- | core/extension/native_extension_manager.h | 3 | ||||
-rw-r--r-- | core/io/config_file.cpp | 2 | ||||
-rw-r--r-- | core/math/geometry_2d.h | 20 | ||||
-rw-r--r-- | core/object/class_db.cpp | 26 | ||||
-rw-r--r-- | core/object/class_db.h | 28 | ||||
-rw-r--r-- | core/object/make_virtuals.py | 2 | ||||
-rw-r--r-- | core/object/object.h | 4 | ||||
-rw-r--r-- | core/register_core_types.cpp | 8 | ||||
-rw-r--r-- | core/templates/local_vector.h | 2 | ||||
-rw-r--r-- | core/templates/safe_list.h | 375 | ||||
-rw-r--r-- | core/variant/array.cpp | 31 | ||||
-rw-r--r-- | core/variant/array.h | 1 | ||||
-rw-r--r-- | core/variant/variant.cpp | 4 | ||||
-rw-r--r-- | core/variant/variant_call.cpp | 1 |
18 files changed, 512 insertions, 19 deletions
diff --git a/core/doc_data.h b/core/doc_data.h index a3011fe275..19dec71927 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -67,6 +67,7 @@ public: String qualifiers; String description; Vector<ArgumentDoc> arguments; + Vector<int> errors_returned; bool operator<(const MethodDoc &p_method) const { if (name == p_method.name) { // Must be a constructor since there is no overloading. diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 0556c9c84d..a3cd7ca14c 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -35,6 +35,8 @@ #include "core/object/method_bind.h" #include "core/os/os.h" +const char *NativeExtension::EXTENSION_LIST_CONFIG_FILE = "res://.godot/extension_list.cfg"; + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index a961b21cc9..b661381d64 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -60,6 +60,8 @@ protected: static void _bind_methods(); public: + static const char *EXTENSION_LIST_CONFIG_FILE; + Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/extension/native_extension_manager.cpp b/core/extension/native_extension_manager.cpp index 7be2593845..8b7a9df4f1 100644 --- a/core/extension/native_extension_manager.cpp +++ b/core/extension/native_extension_manager.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "native_extension_manager.h" +#include "core/io/file_access.h" NativeExtensionManager::LoadStatus NativeExtensionManager::load_extension(const String &p_path) { if (native_extension_map.has(p_path)) { @@ -76,6 +77,11 @@ NativeExtensionManager::LoadStatus NativeExtensionManager::unload_extension(cons native_extension_map.erase(p_path); return LOAD_STATUS_OK; } + +bool NativeExtensionManager::is_extension_loaded(const String &p_path) const { + return native_extension_map.has(p_path); +} + Vector<String> NativeExtensionManager::get_loaded_extensions() const { Vector<String> ret; for (const Map<String, Ref<NativeExtension>>::Element *E = native_extension_map.front(); E; E = E->next()) { @@ -105,6 +111,17 @@ void NativeExtensionManager::deinitialize_extensions(NativeExtension::Initializa level = int32_t(p_level) - 1; } +void NativeExtensionManager::load_extensions() { + FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::READ); + while (f && !f->eof_reached()) { + String s = f->get_line().strip_edges(); + if (s != String()) { + LoadStatus err = load_extension(s); + ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s); + } + } +} + NativeExtensionManager *NativeExtensionManager::get_singleton() { return singleton; } @@ -112,6 +129,8 @@ void NativeExtensionManager::_bind_methods() { ClassDB::bind_method(D_METHOD("load_extension", "path"), &NativeExtensionManager::load_extension); ClassDB::bind_method(D_METHOD("reload_extension", "path"), &NativeExtensionManager::reload_extension); ClassDB::bind_method(D_METHOD("unload_extension", "path"), &NativeExtensionManager::unload_extension); + ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &NativeExtensionManager::is_extension_loaded); + ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &NativeExtensionManager::get_loaded_extensions); ClassDB::bind_method(D_METHOD("get_extension", "path"), &NativeExtensionManager::get_extension); diff --git a/core/extension/native_extension_manager.h b/core/extension/native_extension_manager.h index 78465bd5cf..89ccd155fe 100644 --- a/core/extension/native_extension_manager.h +++ b/core/extension/native_extension_manager.h @@ -55,6 +55,7 @@ public: LoadStatus load_extension(const String &p_path); 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<NativeExtension> get_extension(const String &p_path); @@ -63,6 +64,8 @@ public: static NativeExtensionManager *get_singleton(); + void load_extensions(); + NativeExtensionManager(); }; diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index aeaf25f321..55793aa5a4 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -315,6 +315,8 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "data"), &ConfigFile::parse); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); + BIND_METHOD_ERR_RETURN_DOC("load", ERR_FILE_CANT_OPEN); + ClassDB::bind_method(D_METHOD("load_encrypted", "path", "key"), &ConfigFile::load_encrypted); ClassDB::bind_method(D_METHOD("load_encrypted_pass", "path", "password"), &ConfigFile::load_encrypted_pass); diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index e1a5bfe6f2..8e5830f9b3 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -182,7 +182,15 @@ public: C = Vector2(C.x * Bn.x + C.y * Bn.y, C.y * Bn.x - C.x * Bn.y); D = Vector2(D.x * Bn.x + D.y * Bn.y, D.y * Bn.x - D.x * Bn.y); - if ((C.y < 0 && D.y < 0) || (C.y >= 0 && D.y >= 0)) { + // Fail if C x B and D x B have the same sign (segments don't intersect). + // (equivalent to condition (C.y < 0 && D.y < CMP_EPSILON) || (C.y > 0 && D.y > CMP_EPSILON)) + if (C.y * D.y > CMP_EPSILON) { + return false; + } + + // Fail if segments are parallel or colinear. + // (when A x B == zero, i.e (C - D) x B == zero, i.e C x B == D x B) + if (Math::is_equal_approx(C.y, D.y)) { return false; } @@ -193,7 +201,7 @@ public: return false; } - // (4) Apply the discovered position to line A-B in the original coordinate system. + // Apply the discovered position to line A-B in the original coordinate system. if (r_result) { *r_result = p_from_a + B * ABpos; } @@ -353,8 +361,14 @@ public: for (int i = 0; i < c; i++) { const Vector2 &v1 = p[i]; const Vector2 &v2 = p[(i + 1) % c]; - if (segment_intersects_segment(v1, v2, p_point, further_away, nullptr)) { + + Vector2 res; + if (segment_intersects_segment(v1, v2, p_point, further_away, &res)) { intersections++; + if (res.is_equal_approx(p_point)) { + // Point is in one of the polygon edges. + return true; + } } } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index b29b2bd421..e268a8d292 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -892,6 +892,32 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_ } } +void ClassDB::set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values) { + OBJTYPE_RLOCK; +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND(!type); + + type->method_error_values[p_method] = p_values; +#endif +} + +Vector<Error> ClassDB::get_method_error_return_values(const StringName &p_class, const StringName &p_method) { +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND_V(!type, Vector<Error>()); + + if (!type->method_error_values.has(p_method)) { + return Vector<Error>(); + } + return type->method_error_values[p_method]; +#else + return Vector<Error>(); +#endif +} + bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { OBJTYPE_RLOCK; diff --git a/core/object/class_db.h b/core/object/class_db.h index 45572517be..166aa35469 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -132,6 +132,7 @@ public: List<MethodInfo> virtual_methods; Map<StringName, MethodInfo> virtual_methods_map; StringName category; + Map<StringName, Vector<Error>> method_error_values; #endif HashMap<StringName, PropertySetGet> property_setget; @@ -385,6 +386,8 @@ public: static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false); static bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false); + static void set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values); + static Vector<Error> get_method_error_return_values(const StringName &p_class, const StringName &p_method); static Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr); static StringName get_category(const StringName &p_node); @@ -415,6 +418,29 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant); +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr) { +} + +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err) { + arr.push_back(p_err); +} + +template <class... P> +_FORCE_INLINE_ void errarray_add_str(Vector<Error> &arr, const Error &p_err, P... p_args) { + arr.push_back(p_err); + errarray_add_str(arr, p_args...); +} + +template <class... P> +_FORCE_INLINE_ Vector<Error> errarray(P... p_args) { + Vector<Error> arr; + errarray_add_str(arr, p_args...); + return arr; +} + +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) \ + ::ClassDB::set_method_error_return_values(get_class_static(), m_method, errarray(__VA_ARGS__)); + #else #define BIND_CONSTANT(m_constant) \ @@ -423,6 +449,8 @@ public: #define BIND_ENUM_CONSTANT(m_constant) \ ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); +#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) + #endif #define GDREGISTER_CLASS(m_class) \ diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 65a421cfb2..86c2891e5d 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -23,7 +23,7 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ \\ return false;\\ }\\ -_FORCE_INLINE_ bool _gdvirtual_##m_name##_overriden() const { \\ +_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ if (script_instance) {\\ return script_instance->has_method(_gdvirtual_##m_name##_sn);\\ diff --git a/core/object/object.h b/core/object/object.h index a53143158b..102776a589 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -289,8 +289,8 @@ struct ObjectNativeExtension { #else #define GDVIRTUAL_BIND(m_name, ...) #endif -#define GDVIRTUAL_IS_OVERRIDEN(m_name) _gdvirtual_##m_name##_overriden() -#define GDVIRTUAL_IS_OVERRIDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overriden() +#define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden() +#define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden() /* the following is an incomprehensible blob of hacks and workarounds to compensate for many of the fallencies in C++. As a plus, this macro pretty much alone defines the object model. diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 35f4532abb..6c7d9cbd89 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -308,13 +308,7 @@ void register_core_singletons() { void register_core_extensions() { // Hardcoded for now. NativeExtension::initialize_native_extensions(); - if (ProjectSettings::get_singleton()->has_setting("native_extensions/paths")) { - Vector<String> paths = ProjectSettings::get_singleton()->get("native_extensions/paths"); - for (int i = 0; i < paths.size(); i++) { - NativeExtensionManager::LoadStatus status = native_extension_manager->load_extension(paths[i]); - ERR_CONTINUE_MSG(status != NativeExtensionManager::LOAD_STATUS_OK, "Error loading extension: " + paths[i]); - } - } + native_extension_manager->load_extensions(); native_extension_manager->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_CORE); } diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 668ec513d6..5704b8f230 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -170,7 +170,7 @@ public: push_back(p_val); } else { resize(count + 1); - for (U i = count; i > p_pos; i--) { + for (U i = count - 1; i > p_pos; i--) { data[i] = data[i - 1]; } data[p_pos] = p_val; diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h new file mode 100644 index 0000000000..d8f010663b --- /dev/null +++ b/core/templates/safe_list.h @@ -0,0 +1,375 @@ +/*************************************************************************/ +/* safe_list.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SAFE_LIST_H +#define SAFE_LIST_H + +#include "core/os/memory.h" +#include "core/typedefs.h" +#include <functional> + +#if !defined(NO_THREADS) + +#include <atomic> +#include <type_traits> + +// Design goals for these classes: +// - Accessing this list with an iterator will never result in a use-after free, +// even if the element being accessed has been logically removed from the list on +// another thread. +// - Logical deletion from the list will not result in deallocation at that time, +// instead the node will be deallocated at a later time when it is safe to do so. +// - No blocking synchronization primitives will be used. + +// This is used in very specific areas of the engine where it's critical that these guarantees are held. + +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + std::atomic<SafeListNode *> next = nullptr; + + // If the node is logically deleted, this pointer will typically point + // to the previous list item in time that was also logically deleted. + std::atomic<SafeListNode *> graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + static_assert(std::atomic<T>::is_always_lock_free); + + std::atomic<SafeListNode *> head = nullptr; + std::atomic<SafeListNode *> graveyard_head = nullptr; + + std::atomic_uint active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + public: + Iterator(const Iterator &p_other) : + cursor(p_other.cursor), list(p_other.list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + public: + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + SafeListNode *expected_head = nullptr; + do { + expected_head = head.load(); + new_node->next.store(expected_head); + } while (!head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ new_node)); + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + Iterator tmp = find(p_value); + erase(tmp, p_deletion_fn); + } + + void erase(T p_value) { + Iterator tmp = find(p_value); + erase(tmp, [](T t) { return; }); + } + + void erase(Iterator &p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator &p_iterator) { + if (find(p_iterator.cursor->val) == nullptr) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + while (true) { + Iterator prev = begin(); + SafeListNode *expected_head = prev.cursor; + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev != end()) { + // There exists a node before this. + prev.cursor->next.store(p_iterator.cursor->next.load()); + // Done. + break; + } else { + if (head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor->next.load())) { + // Successfully reassigned the head pointer before another thread changed it to something else. + break; + } + // Fall through upon failure, try again. + } + } + // Then queue it for deletion by putting it in the node graveyard. + // Don't touch `next` because an iterator might still be pointing at this node. + SafeListNode *expected_head = nullptr; + do { + expected_head = graveyard_head.load(); + p_iterator.cursor->graveyard_next.store(expected_head); + } while (!graveyard_head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor)); + } + + Iterator begin() { + return Iterator(head.load(), this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = nullptr; + SafeListNode *new_graveyard_head = nullptr; + do { + // The access order here is theoretically important. + cursor = graveyard_head.load(); + if (active_iterator_count.load() != 0) { + // It's not safe to clean up with an active iterator, because that iterator + // could be pointing to an element that we want to delete. + return; + } + // Any iterator created after this point will never point to a deleted node. + // Swap it out with the current graveyard head. + } while (!graveyard_head.compare_exchange_strong(/* expected= */ cursor, /* new= */ new_graveyard_head)); + // Our graveyard list is now unreachable by any active iterators, + // detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->graveyard_next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#else // NO_THREADS + +// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly. +template <class T, class A = DefaultAllocator> +class SafeList { + struct SafeListNode { + SafeListNode *next = nullptr; + + // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted. + SafeListNode *graveyard_next = nullptr; + + std::function<void(T)> deletion_fn = [](T t) { return; }; + + T val; + }; + + SafeListNode *head = nullptr; + SafeListNode *graveyard_head = nullptr; + + unsigned int active_iterator_count = 0; + +public: + class Iterator { + friend class SafeList; + + SafeListNode *cursor; + SafeList *list; + + public: + Iterator(SafeListNode *p_cursor, SafeList *p_list) : + cursor(p_cursor), list(p_list) { + list->active_iterator_count++; + } + + ~Iterator() { + list->active_iterator_count--; + } + + T &operator*() { + return cursor->val; + } + + Iterator &operator++() { + cursor = cursor->next; + return *this; + } + + // These two operators are mostly useful for comparisons to nullptr. + bool operator==(const void *p_other) const { + return cursor == p_other; + } + + bool operator!=(const void *p_other) const { + return cursor != p_other; + } + + // These two allow easy range-based for loops. + bool operator==(const Iterator &p_other) const { + return cursor == p_other.cursor; + } + + bool operator!=(const Iterator &p_other) const { + return cursor != p_other.cursor; + } + }; + +public: + // Calling this will cause an allocation. + void insert(T p_value) { + SafeListNode *new_node = memnew_allocator(SafeListNode, A); + new_node->val = p_value; + new_node->next = head; + head = new_node; + } + + Iterator find(T p_value) { + for (Iterator it = begin(); it != end(); ++it) { + if (*it == p_value) { + return it; + } + } + return end(); + } + + void erase(T p_value, std::function<void(T)> p_deletion_fn) { + erase(find(p_value), p_deletion_fn); + } + + void erase(T p_value) { + erase(find(p_value), [](T t) { return; }); + } + + void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) { + p_iterator.cursor->deletion_fn = p_deletion_fn; + erase(p_iterator); + } + + void erase(Iterator p_iterator) { + Iterator prev = begin(); + for (; prev != end(); ++prev) { + if (prev.cursor && prev.cursor->next == p_iterator.cursor) { + break; + } + } + if (prev == end()) { + // Not in the list, nothing to do. + return; + } + // First, remove the node from the list. + prev.cursor->next = p_iterator.cursor->next; + + // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node. + p_iterator.cursor->graveyard_next = graveyard_head; + graveyard_head = p_iterator.cursor; + } + + Iterator begin() { + return Iterator(head, this); + } + + Iterator end() { + return Iterator(nullptr, this); + } + + // Calling this will cause zero to many deallocations. + void maybe_cleanup() { + SafeListNode *cursor = graveyard_head; + if (active_iterator_count != 0) { + // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete. + return; + } + graveyard_head = nullptr; + // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion. + while (cursor) { + SafeListNode *tmp = cursor; + cursor = cursor->next; + tmp->deletion_fn(tmp->val); + memdelete_allocator<SafeListNode, A>(tmp); + } + } +}; + +#endif + +#endif // SAFE_LIST_H diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 09cf785390..78ad796283 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -535,8 +535,8 @@ void Array::push_front(const Variant &p_value) { Variant Array::pop_back() { if (!_p->array.is_empty()) { - int n = _p->array.size() - 1; - Variant ret = _p->array.get(n); + const int n = _p->array.size() - 1; + const Variant ret = _p->array.get(n); _p->array.resize(n); return ret; } @@ -545,13 +545,38 @@ Variant Array::pop_back() { Variant Array::pop_front() { if (!_p->array.is_empty()) { - Variant ret = _p->array.get(0); + const Variant ret = _p->array.get(0); _p->array.remove(0); return ret; } return Variant(); } +Variant Array::pop_at(int p_pos) { + if (_p->array.is_empty()) { + // Return `null` without printing an error to mimic `pop_back()` and `pop_front()` behavior. + return Variant(); + } + + if (p_pos < 0) { + // Relative offset from the end + p_pos = _p->array.size() + p_pos; + } + + ERR_FAIL_INDEX_V_MSG( + p_pos, + _p->array.size(), + Variant(), + vformat( + "The calculated index %s is out of bounds (the array has %s elements). Leaving the array untouched and returning `null`.", + p_pos, + _p->array.size())); + + const Variant ret = _p->array.get(p_pos); + _p->array.remove(p_pos); + return ret; +} + Variant Array::min() const { Variant minval; for (int i = 0; i < size(); i++) { diff --git a/core/variant/array.h b/core/variant/array.h index 540dcb1f4e..e9634ccece 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -97,6 +97,7 @@ public: void push_front(const Variant &p_value); Variant pop_back(); Variant pop_front(); + Variant pop_at(int p_pos); Array duplicate(bool p_deep = false) const; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index d538b9faff..c5cada674f 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1627,9 +1627,9 @@ Variant::operator String() const { String Variant::stringify(List<const void *> &stack) const { switch (type) { case NIL: - return "Null"; + return "null"; case BOOL: - return _data._bool ? "True" : "False"; + return _data._bool ? "true" : "false"; case INT: return itos(_data._int); case FLOAT: diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index a4817eb7d2..382b256c08 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1807,6 +1807,7 @@ static void _register_variant_builtin_methods() { bind_method(Array, has, sarray("value"), varray()); bind_method(Array, pop_back, sarray(), varray()); bind_method(Array, pop_front, sarray(), varray()); + bind_method(Array, pop_at, sarray("position"), varray()); bind_method(Array, sort, sarray(), varray()); bind_method(Array, sort_custom, sarray("func"), varray()); bind_method(Array, shuffle, sarray(), varray()); |