From d8363490f5df986e3aae7a9ca6815dcc6013e33f Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Thu, 10 Sep 2020 18:21:08 +0300 Subject: [PATCH 01/25] Initial implementation of `List` for scripting purposes --- core/SCsub | 1 + core/bind/list.cpp | 125 ++++++++++++++++++ core/bind/list.h | 183 ++++++++++++++++++++++++++ core/register_core_types.cpp | 4 + tests/project/goost/core/test_list.gd | 11 ++ 5 files changed, 324 insertions(+) create mode 100644 core/bind/list.cpp create mode 100644 core/bind/list.h create mode 100644 tests/project/goost/core/test_list.gd diff --git a/core/SCsub b/core/SCsub index 3975ca8e..50e73212 100644 --- a/core/SCsub +++ b/core/SCsub @@ -9,3 +9,4 @@ if env["goost_math_enabled"]: SConscript("math/SCsub", exports="env_goost") env_goost.add_source_files(env.modules_sources, "*.cpp") +env_goost.add_source_files(env.modules_sources, "bind/*.cpp") diff --git a/core/bind/list.cpp b/core/bind/list.cpp new file mode 100644 index 00000000..8f4d2b32 --- /dev/null +++ b/core/bind/list.cpp @@ -0,0 +1,125 @@ +#include "list.h" + +bool VariantListData::erase(VariantListElement *p_I) { + ERR_FAIL_COND_V(!p_I, false); + ERR_FAIL_COND_V(p_I->data != this, false); + + if (first == p_I) { + first = p_I->next_ptr; + } + if (last == p_I) { + last = p_I->prev_ptr; + } + if (p_I->prev_ptr) { + p_I->prev_ptr->next_ptr = p_I->next_ptr; + } + if (p_I->next_ptr) { + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + } + memdelete(p_I); + size_cache--; + + return true; +} + +VariantListElement *VariantList::front() { + return _data ? _data->first : 0; +} + +VariantListElement *VariantList::back() { + return _data ? _data->last : 0; +} + +VariantListElement* VariantList::push_back(const Variant &value) { + if (!_data) { + _data = memnew_allocator(VariantListData, DefaultAllocator); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + VariantListElement *n = memnew(VariantListElement); + n->value = value; + + n->prev_ptr = _data->last; + n->next_ptr = 0; + n->data = _data; + + if (_data->last) { + _data->last->next_ptr = n; + } + _data->last = n; + + if (!_data->first) { + _data->first = n; + } + _data->size_cache++; + + return n; +} + +void VariantList::pop_back() { + if (_data && _data->last) { + erase(_data->last); + } +} + +VariantListElement *VariantList::push_front(const Variant &value) { + if (!_data) { + _data = memnew_allocator(VariantListData, DefaultAllocator); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + VariantListElement *n = memnew_allocator(VariantListElement, DefaultAllocator); + n->value = value; + n->prev_ptr = 0; + n->next_ptr = _data->first; + n->data = _data; + + if (_data->first) { + _data->first->prev_ptr = n; + } + _data->first = n; + + if (!_data->last) { + _data->last = n; + } + _data->size_cache++; + + return n; +} + +void VariantList::pop_front() { + if (_data && _data->first) { + erase(_data->first); + } +} + +bool VariantList::erase(VariantListElement *p_I) { + if (_data) { + bool ret = _data->erase(p_I); + if (_data->size_cache == 0) { + memdelete_allocator(_data); + _data = nullptr; + } + return ret; + } + return false; +}; + +void VariantList::_bind_methods() { + ClassDB::bind_method(D_METHOD("front"), &VariantList::front); + ClassDB::bind_method(D_METHOD("back"), &VariantList::back); + ClassDB::bind_method(D_METHOD("push_back", "value"), &VariantList::push_back); + ClassDB::bind_method(D_METHOD("pop_back"), &VariantList::pop_back); + ClassDB::bind_method(D_METHOD("push_front", "value"), &VariantList::push_front); + ClassDB::bind_method(D_METHOD("pop_front"), &VariantList::pop_front); + ClassDB::bind_method(D_METHOD("erase", "element"), &VariantList::erase); +} + +void VariantListElement::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_value", "value"), &VariantListElement::set_value); + ClassDB::bind_method(D_METHOD("get_value"), &VariantListElement::get_value); + + ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); +} diff --git a/core/bind/list.h b/core/bind/list.h new file mode 100644 index 00000000..2a093fbc --- /dev/null +++ b/core/bind/list.h @@ -0,0 +1,183 @@ +#ifndef GOOST_LIST_H +#define GOOST_LIST_H + +// Implementation based on Godot's `core/list.h`, which is mostly +// an instantiation of the `List` template class for performance, +// which also allows to register relevant classes to scripting. + +#include "core/object.h" +#include "core/sort_array.h" + +class VariantListElement; + +struct VariantListData { + VariantListElement *first = nullptr; + VariantListElement *last = nullptr; + int size_cache = 0; + bool erase(VariantListElement *p_I); +}; + +class VariantListElement : public Object { + GDCLASS(VariantListElement, Object); + +protected: + static void _bind_methods(); + +private: + friend class VariantList; + friend struct VariantListData; + + Variant value; + VariantListElement *next_ptr = nullptr; + VariantListElement *prev_ptr = nullptr; + VariantListData *data = nullptr; + +public: + VariantListElement *next(); + VariantListElement *prev(); + Variant get_value() { return value; } + void set_value(const Variant &p_value) { value = p_value; } + void erase() { data->erase(this); } + + VariantListElement() {} +}; + +class VariantList : public Object { + GDCLASS(VariantList, Object); + +protected: + static void _bind_methods(); + +private: + VariantListData *_data = nullptr; + +public: + VariantListElement *front(); + VariantListElement *back(); + VariantListElement *push_back(const Variant &value); + void pop_back(); + VariantListElement *push_front(const Variant &value); + void pop_front(); + + VariantListElement *insert_after(VariantListElement *p_element, const Variant &p_value); + VariantListElement *insert_before(VariantListElement *p_element, const Variant &p_value); + + VariantListElement *find(const Variant &p_val); + + bool erase(VariantListElement *p_I); + // bool erase(const Variant &value); // TODO: rename to erase_first_found? + bool empty() const; + void clear(); + int size() const; + + void swap(VariantListElement *p_A, VariantListElement *p_B); + + void move_to_back(VariantListElement *p_I); + + void invert(); + + void move_to_front(VariantListElement *p_I); + void move_before(VariantListElement *value, VariantListElement *where); + + void sort() { + sort_custom >(); + } + + template + void sort_custom_inplace() { + + if (size() < 2) + return; + + VariantListElement *from = front(); + VariantListElement *current = from; + VariantListElement *to = from; + + while (current) { + + VariantListElement *next = current->next_ptr; + + if (from != current) { + + current->prev_ptr = nullptr; + current->next_ptr = from; + + VariantListElement *find = from; + C less; + while (find && less(find->value, current->value)) { + + current->prev_ptr = find; + current->next_ptr = find->next_ptr; + find = find->next_ptr; + } + + if (current->prev_ptr) + current->prev_ptr->next_ptr = current; + else + from = current; + + if (current->next_ptr) + current->next_ptr->prev_ptr = current; + else + to = current; + } else { + + current->prev_ptr = nullptr; + current->next_ptr = nullptr; + } + + current = next; + } + _data->first = from; + _data->last = to; + } + + template + struct AuxiliaryComparator { + + C compare; + bool operator()(const VariantListElement *a, const VariantListElement *b) const { + + return compare(a->value, b->value); + } + }; + + template + void sort_custom() { + int s = size(); + if (s < 2) + return; + + VariantListElement **aux_buffer = memnew_arr(VariantListElement *, s); + + int idx = 0; + for (VariantListElement *E = front(); E; E = E->next_ptr) { + + aux_buffer[idx] = E; + idx++; + } + + SortArray > sort; + sort.sort(aux_buffer, s); + + _data->first = aux_buffer[0]; + aux_buffer[0]->prev_ptr = nullptr; + aux_buffer[0]->next_ptr = aux_buffer[1]; + + _data->last = aux_buffer[s - 1]; + aux_buffer[s - 1]->prev_ptr = aux_buffer[s - 2]; + aux_buffer[s - 1]->next_ptr = nullptr; + + for (int i = 1; i < s - 1; i++) { + + aux_buffer[i]->prev_ptr = aux_buffer[i - 1]; + aux_buffer[i]->next_ptr = aux_buffer[i + 1]; + } + + memdelete_arr(aux_buffer); + } + // VariantList(const VariantList &p_list); + VariantList() {} +}; + +#endif // GOOST_LIST_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 029f322e..d3d9971f 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -1,11 +1,15 @@ #include "register_core_types.h" +#include "core/bind/list.h" + #include "image/register_image_types.h" #include "math/register_math_types.h" namespace goost { void register_core_types() { + ClassDB::register_class(); + ClassDB::register_class(); #ifdef GOOST_IMAGE_ENABLED register_image_types(); #endif diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd new file mode 100644 index 00000000..16257804 --- /dev/null +++ b/tests/project/goost/core/test_list.gd @@ -0,0 +1,11 @@ +extends "res://addons/gut/test.gd" + +func test_push_back(): + var list = VariantList.new() + var n: VariantListElement + n = list.push_back("Goost") + assert_eq(n.value, "Goost") + n = list.push_back(37) + assert_eq(n.value, 37) + n = list.push_back(Color.red) + assert_eq(n.value, Color.red) From 937da92bb808e7d20e92f0be41f81e9dd2e2d8ed Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Thu, 10 Sep 2020 19:26:34 +0300 Subject: [PATCH 02/25] Teach `Variant` to convert `VariantListElement` --- core/bind/list.h | 66 +++++++++++++-------------- tests/project/goost/core/test_list.gd | 20 +++++++- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/core/bind/list.h b/core/bind/list.h index 2a093fbc..ed6ce02a 100644 --- a/core/bind/list.h +++ b/core/bind/list.h @@ -11,42 +11,42 @@ class VariantListElement; struct VariantListData { - VariantListElement *first = nullptr; - VariantListElement *last = nullptr; - int size_cache = 0; - bool erase(VariantListElement *p_I); + VariantListElement *first = nullptr; + VariantListElement *last = nullptr; + int size_cache = 0; + bool erase(VariantListElement *p_I); }; class VariantListElement : public Object { - GDCLASS(VariantListElement, Object); - + GDCLASS(VariantListElement, Object); + protected: - static void _bind_methods(); - + static void _bind_methods(); + private: - friend class VariantList; - friend struct VariantListData; + friend class VariantList; + friend struct VariantListData; - Variant value; - VariantListElement *next_ptr = nullptr; - VariantListElement *prev_ptr = nullptr; - VariantListData *data = nullptr; + Variant value; + VariantListElement *next_ptr = nullptr; + VariantListElement *prev_ptr = nullptr; + VariantListData *data = nullptr; public: - VariantListElement *next(); - VariantListElement *prev(); - Variant get_value() { return value; } - void set_value(const Variant &p_value) { value = p_value; } - void erase() { data->erase(this); } + VariantListElement *next(); + VariantListElement *prev(); + Variant get_value() { return value; } + void set_value(const Variant &p_value) { value = p_value; } + void erase() { data->erase(this); } - VariantListElement() {} + VariantListElement() {} }; class VariantList : public Object { - GDCLASS(VariantList, Object); - + GDCLASS(VariantList, Object); + protected: - static void _bind_methods(); + static void _bind_methods(); private: VariantListData *_data = nullptr; @@ -80,12 +80,11 @@ class VariantList : public Object { void move_before(VariantListElement *value, VariantListElement *where); void sort() { - sort_custom >(); + sort_custom>(); } template void sort_custom_inplace() { - if (size() < 2) return; @@ -94,18 +93,15 @@ class VariantList : public Object { VariantListElement *to = from; while (current) { - VariantListElement *next = current->next_ptr; if (from != current) { - current->prev_ptr = nullptr; current->next_ptr = from; VariantListElement *find = from; C less; while (find && less(find->value, current->value)) { - current->prev_ptr = find; current->next_ptr = find->next_ptr; find = find->next_ptr; @@ -121,7 +117,6 @@ class VariantList : public Object { else to = current; } else { - current->prev_ptr = nullptr; current->next_ptr = nullptr; } @@ -134,10 +129,8 @@ class VariantList : public Object { template struct AuxiliaryComparator { - C compare; bool operator()(const VariantListElement *a, const VariantListElement *b) const { - return compare(a->value, b->value); } }; @@ -152,12 +145,11 @@ class VariantList : public Object { int idx = 0; for (VariantListElement *E = front(); E; E = E->next_ptr) { - aux_buffer[idx] = E; idx++; } - SortArray > sort; + SortArray> sort; sort.sort(aux_buffer, s); _data->first = aux_buffer[0]; @@ -169,7 +161,6 @@ class VariantList : public Object { aux_buffer[s - 1]->next_ptr = nullptr; for (int i = 1; i < s - 1; i++) { - aux_buffer[i]->prev_ptr = aux_buffer[i - 1]; aux_buffer[i]->next_ptr = aux_buffer[i + 1]; } @@ -180,4 +171,11 @@ class VariantList : public Object { VariantList() {} }; +template <> +struct VariantCaster { + static _FORCE_INLINE_ VariantListElement* cast(const Variant &p_variant) { + return (VariantListElement*)p_variant.operator Object*(); + } +}; + #endif // GOOST_LIST_H diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd index 16257804..4b67aa66 100644 --- a/tests/project/goost/core/test_list.gd +++ b/tests/project/goost/core/test_list.gd @@ -1,7 +1,10 @@ extends "res://addons/gut/test.gd" -func test_push_back(): + +func test_push_pop_back(): var list = VariantList.new() + + # Push back var n: VariantListElement n = list.push_back("Goost") assert_eq(n.value, "Goost") @@ -9,3 +12,18 @@ func test_push_back(): assert_eq(n.value, 37) n = list.push_back(Color.red) assert_eq(n.value, Color.red) + + # Pop back + var v = null + v = list.back().value + list.pop_back() + assert_eq(v, Color.red) + v = list.back().value + list.pop_back() + assert_eq(v, 37) + v = list.back().value + list.pop_back() + assert_eq(v, "Goost") + + assert_null(list.back()) + assert_null(list.front()) From 7463b9e024dbff129affeb9da2deb449bc6f0091 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Thu, 10 Sep 2020 19:58:02 +0300 Subject: [PATCH 03/25] Rename `VariantList` to `LinkedList` A bit too verbose, and `LinkedList` is more common name in other languages. Not to mention that `Array` already handles `Variant` types. --- core/bind/list.cpp | 208 +++++++++++++------------- core/bind/list.h | 86 +++++------ core/register_core_types.cpp | 4 +- tests/project/goost/core/test_list.gd | 4 +- 4 files changed, 151 insertions(+), 151 deletions(-) diff --git a/core/bind/list.cpp b/core/bind/list.cpp index 8f4d2b32..cd23f757 100644 --- a/core/bind/list.cpp +++ b/core/bind/list.cpp @@ -1,125 +1,125 @@ #include "list.h" -bool VariantListData::erase(VariantListElement *p_I) { - ERR_FAIL_COND_V(!p_I, false); - ERR_FAIL_COND_V(p_I->data != this, false); - - if (first == p_I) { - first = p_I->next_ptr; - } - if (last == p_I) { - last = p_I->prev_ptr; - } - if (p_I->prev_ptr) { - p_I->prev_ptr->next_ptr = p_I->next_ptr; - } - if (p_I->next_ptr) { - p_I->next_ptr->prev_ptr = p_I->prev_ptr; - } - memdelete(p_I); - size_cache--; - - return true; +bool ListData::erase(ListElement *p_I) { + ERR_FAIL_COND_V(!p_I, false); + ERR_FAIL_COND_V(p_I->data != this, false); + + if (first == p_I) { + first = p_I->next_ptr; + } + if (last == p_I) { + last = p_I->prev_ptr; + } + if (p_I->prev_ptr) { + p_I->prev_ptr->next_ptr = p_I->next_ptr; + } + if (p_I->next_ptr) { + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + } + memdelete(p_I); + size_cache--; + + return true; } -VariantListElement *VariantList::front() { - return _data ? _data->first : 0; +ListElement *LinkedList::front() { + return _data ? _data->first : 0; } -VariantListElement *VariantList::back() { - return _data ? _data->last : 0; +ListElement *LinkedList::back() { + return _data ? _data->last : 0; } -VariantListElement* VariantList::push_back(const Variant &value) { - if (!_data) { - _data = memnew_allocator(VariantListData, DefaultAllocator); - _data->first = nullptr; - _data->last = nullptr; - _data->size_cache = 0; - } - VariantListElement *n = memnew(VariantListElement); - n->value = value; - - n->prev_ptr = _data->last; - n->next_ptr = 0; - n->data = _data; - - if (_data->last) { - _data->last->next_ptr = n; - } - _data->last = n; - - if (!_data->first) { - _data->first = n; - } - _data->size_cache++; - - return n; +ListElement *LinkedList::push_back(const Variant &value) { + if (!_data) { + _data = memnew_allocator(ListData, DefaultAllocator); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + ListElement *n = memnew(ListElement); + n->value = value; + + n->prev_ptr = _data->last; + n->next_ptr = 0; + n->data = _data; + + if (_data->last) { + _data->last->next_ptr = n; + } + _data->last = n; + + if (!_data->first) { + _data->first = n; + } + _data->size_cache++; + + return n; } -void VariantList::pop_back() { - if (_data && _data->last) { - erase(_data->last); - } +void LinkedList::pop_back() { + if (_data && _data->last) { + erase(_data->last); + } } -VariantListElement *VariantList::push_front(const Variant &value) { - if (!_data) { - _data = memnew_allocator(VariantListData, DefaultAllocator); - _data->first = nullptr; - _data->last = nullptr; - _data->size_cache = 0; - } - VariantListElement *n = memnew_allocator(VariantListElement, DefaultAllocator); - n->value = value; - n->prev_ptr = 0; - n->next_ptr = _data->first; - n->data = _data; - - if (_data->first) { - _data->first->prev_ptr = n; - } - _data->first = n; - - if (!_data->last) { - _data->last = n; - } - _data->size_cache++; - - return n; +ListElement *LinkedList::push_front(const Variant &value) { + if (!_data) { + _data = memnew_allocator(ListData, DefaultAllocator); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + ListElement *n = memnew_allocator(ListElement, DefaultAllocator); + n->value = value; + n->prev_ptr = 0; + n->next_ptr = _data->first; + n->data = _data; + + if (_data->first) { + _data->first->prev_ptr = n; + } + _data->first = n; + + if (!_data->last) { + _data->last = n; + } + _data->size_cache++; + + return n; } -void VariantList::pop_front() { - if (_data && _data->first) { - erase(_data->first); - } +void LinkedList::pop_front() { + if (_data && _data->first) { + erase(_data->first); + } } -bool VariantList::erase(VariantListElement *p_I) { - if (_data) { - bool ret = _data->erase(p_I); - if (_data->size_cache == 0) { - memdelete_allocator(_data); - _data = nullptr; - } - return ret; - } - return false; +bool LinkedList::erase(ListElement *p_I) { + if (_data) { + bool ret = _data->erase(p_I); + if (_data->size_cache == 0) { + memdelete_allocator(_data); + _data = nullptr; + } + return ret; + } + return false; }; -void VariantList::_bind_methods() { - ClassDB::bind_method(D_METHOD("front"), &VariantList::front); - ClassDB::bind_method(D_METHOD("back"), &VariantList::back); - ClassDB::bind_method(D_METHOD("push_back", "value"), &VariantList::push_back); - ClassDB::bind_method(D_METHOD("pop_back"), &VariantList::pop_back); - ClassDB::bind_method(D_METHOD("push_front", "value"), &VariantList::push_front); - ClassDB::bind_method(D_METHOD("pop_front"), &VariantList::pop_front); - ClassDB::bind_method(D_METHOD("erase", "element"), &VariantList::erase); +void LinkedList::_bind_methods() { + ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); + ClassDB::bind_method(D_METHOD("back"), &LinkedList::back); + ClassDB::bind_method(D_METHOD("push_back", "value"), &LinkedList::push_back); + ClassDB::bind_method(D_METHOD("pop_back"), &LinkedList::pop_back); + ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); + ClassDB::bind_method(D_METHOD("pop_front"), &LinkedList::pop_front); + ClassDB::bind_method(D_METHOD("erase", "element"), &LinkedList::erase); } -void VariantListElement::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_value", "value"), &VariantListElement::set_value); - ClassDB::bind_method(D_METHOD("get_value"), &VariantListElement::get_value); - - ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); +void ListElement::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_value", "value"), &ListElement::set_value); + ClassDB::bind_method(D_METHOD("get_value"), &ListElement::get_value); + + ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); } diff --git a/core/bind/list.h b/core/bind/list.h index ed6ce02a..81fadece 100644 --- a/core/bind/list.h +++ b/core/bind/list.h @@ -8,76 +8,76 @@ #include "core/object.h" #include "core/sort_array.h" -class VariantListElement; +class ListElement; -struct VariantListData { - VariantListElement *first = nullptr; - VariantListElement *last = nullptr; +struct ListData { + ListElement *first = nullptr; + ListElement *last = nullptr; int size_cache = 0; - bool erase(VariantListElement *p_I); + bool erase(ListElement *p_I); }; -class VariantListElement : public Object { - GDCLASS(VariantListElement, Object); +class ListElement : public Object { + GDCLASS(ListElement, Object); protected: static void _bind_methods(); private: - friend class VariantList; - friend struct VariantListData; + friend class LinkedList; + friend struct ListData; Variant value; - VariantListElement *next_ptr = nullptr; - VariantListElement *prev_ptr = nullptr; - VariantListData *data = nullptr; + ListElement *next_ptr = nullptr; + ListElement *prev_ptr = nullptr; + ListData *data = nullptr; public: - VariantListElement *next(); - VariantListElement *prev(); + ListElement *next(); + ListElement *prev(); Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } void erase() { data->erase(this); } - VariantListElement() {} + ListElement() {} }; -class VariantList : public Object { - GDCLASS(VariantList, Object); +class LinkedList : public Object { + GDCLASS(LinkedList, Object); protected: static void _bind_methods(); private: - VariantListData *_data = nullptr; + ListData *_data = nullptr; public: - VariantListElement *front(); - VariantListElement *back(); - VariantListElement *push_back(const Variant &value); + ListElement *front(); + ListElement *back(); + ListElement *push_back(const Variant &value); void pop_back(); - VariantListElement *push_front(const Variant &value); + ListElement *push_front(const Variant &value); void pop_front(); - VariantListElement *insert_after(VariantListElement *p_element, const Variant &p_value); - VariantListElement *insert_before(VariantListElement *p_element, const Variant &p_value); + ListElement *insert_after(ListElement *p_element, const Variant &p_value); + ListElement *insert_before(ListElement *p_element, const Variant &p_value); - VariantListElement *find(const Variant &p_val); + ListElement *find(const Variant &p_val); - bool erase(VariantListElement *p_I); + bool erase(ListElement *p_I); // bool erase(const Variant &value); // TODO: rename to erase_first_found? bool empty() const; void clear(); int size() const; - void swap(VariantListElement *p_A, VariantListElement *p_B); + void swap(ListElement *p_A, ListElement *p_B); - void move_to_back(VariantListElement *p_I); + void move_to_back(ListElement *p_I); void invert(); - void move_to_front(VariantListElement *p_I); - void move_before(VariantListElement *value, VariantListElement *where); + void move_to_front(ListElement *p_I); + void move_before(ListElement *value, ListElement *where); void sort() { sort_custom>(); @@ -88,18 +88,18 @@ class VariantList : public Object { if (size() < 2) return; - VariantListElement *from = front(); - VariantListElement *current = from; - VariantListElement *to = from; + ListElement *from = front(); + ListElement *current = from; + ListElement *to = from; while (current) { - VariantListElement *next = current->next_ptr; + ListElement *next = current->next_ptr; if (from != current) { current->prev_ptr = nullptr; current->next_ptr = from; - VariantListElement *find = from; + ListElement *find = from; C less; while (find && less(find->value, current->value)) { current->prev_ptr = find; @@ -130,7 +130,7 @@ class VariantList : public Object { template struct AuxiliaryComparator { C compare; - bool operator()(const VariantListElement *a, const VariantListElement *b) const { + bool operator()(const ListElement *a, const ListElement *b) const { return compare(a->value, b->value); } }; @@ -141,15 +141,15 @@ class VariantList : public Object { if (s < 2) return; - VariantListElement **aux_buffer = memnew_arr(VariantListElement *, s); + ListElement **aux_buffer = memnew_arr(ListElement *, s); int idx = 0; - for (VariantListElement *E = front(); E; E = E->next_ptr) { + for (ListElement *E = front(); E; E = E->next_ptr) { aux_buffer[idx] = E; idx++; } - SortArray> sort; + SortArray> sort; sort.sort(aux_buffer, s); _data->first = aux_buffer[0]; @@ -168,13 +168,13 @@ class VariantList : public Object { memdelete_arr(aux_buffer); } // VariantList(const VariantList &p_list); - VariantList() {} + LinkedList() {} }; template <> -struct VariantCaster { - static _FORCE_INLINE_ VariantListElement* cast(const Variant &p_variant) { - return (VariantListElement*)p_variant.operator Object*(); +struct VariantCaster { + static _FORCE_INLINE_ ListElement *cast(const Variant &p_variant) { + return (ListElement *)p_variant.operator Object *(); } }; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index d3d9971f..6170edf2 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -8,8 +8,8 @@ namespace goost { void register_core_types() { - ClassDB::register_class(); - ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); #ifdef GOOST_IMAGE_ENABLED register_image_types(); #endif diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd index 4b67aa66..d91a3610 100644 --- a/tests/project/goost/core/test_list.gd +++ b/tests/project/goost/core/test_list.gd @@ -2,10 +2,10 @@ extends "res://addons/gut/test.gd" func test_push_pop_back(): - var list = VariantList.new() + var list = LinkedList.new() # Push back - var n: VariantListElement + var n: ListElement n = list.push_back("Goost") assert_eq(n.value, "Goost") n = list.push_back(37) From c375b623258820b87e25da31890ad0748877f854 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Thu, 10 Sep 2020 20:22:01 +0300 Subject: [PATCH 04/25] Convert `LinkedList` to `Reference` Automatically cleanup allocated data from elements, and the elements themselves. --- core/bind/list.cpp | 14 ++++++++++++++ core/bind/list.h | 8 ++++---- tests/project/goost/core/test_list.gd | 9 +++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/core/bind/list.cpp b/core/bind/list.cpp index cd23f757..cb8caf74 100644 --- a/core/bind/list.cpp +++ b/core/bind/list.cpp @@ -117,9 +117,23 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("erase", "element"), &LinkedList::erase); } +void LinkedList::clear() { + while (front()) { + erase(front()); + } +} + void ListElement::_bind_methods() { ClassDB::bind_method(D_METHOD("set_value", "value"), &ListElement::set_value); ClassDB::bind_method(D_METHOD("get_value"), &ListElement::get_value); ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); } + +LinkedList::~LinkedList() { + clear(); + if (_data) { + ERR_FAIL_COND(_data->size_cache); + memdelete_allocator(_data); + } +} diff --git a/core/bind/list.h b/core/bind/list.h index 81fadece..9d7db977 100644 --- a/core/bind/list.h +++ b/core/bind/list.h @@ -5,7 +5,7 @@ // an instantiation of the `List` template class for performance, // which also allows to register relevant classes to scripting. -#include "core/object.h" +#include "core/reference.h" #include "core/sort_array.h" class ListElement; @@ -42,8 +42,8 @@ class ListElement : public Object { ListElement() {} }; -class LinkedList : public Object { - GDCLASS(LinkedList, Object); +class LinkedList : public Reference { + GDCLASS(LinkedList, Reference); protected: static void _bind_methods(); @@ -167,8 +167,8 @@ class LinkedList : public Object { memdelete_arr(aux_buffer); } - // VariantList(const VariantList &p_list); LinkedList() {} + ~LinkedList(); }; template <> diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd index d91a3610..009fbff4 100644 --- a/tests/project/goost/core/test_list.gd +++ b/tests/project/goost/core/test_list.gd @@ -27,3 +27,12 @@ func test_push_pop_back(): assert_null(list.back()) assert_null(list.front()) + + +func test_cleanup(): + var list = LinkedList.new() + var n = list.push_back("Goost") + assert_not_null(n) + list.unreference() + list = null + assert_null(n) From c8195825928a25a171bcf97b2c851bc1022e793e Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Thu, 10 Sep 2020 22:05:37 +0300 Subject: [PATCH 05/25] Implement basic methods --- core/bind/list.cpp | 38 ++++++-- core/bind/list.h | 16 ++-- tests/project/goost/core/test_list.gd | 120 ++++++++++++++++++++++++-- 3 files changed, 155 insertions(+), 19 deletions(-) diff --git a/core/bind/list.cpp b/core/bind/list.cpp index cb8caf74..64c05b70 100644 --- a/core/bind/list.cpp +++ b/core/bind/list.cpp @@ -59,7 +59,7 @@ ListElement *LinkedList::push_back(const Variant &value) { void LinkedList::pop_back() { if (_data && _data->last) { - erase(_data->last); + remove(_data->last); } } @@ -91,11 +91,11 @@ ListElement *LinkedList::push_front(const Variant &value) { void LinkedList::pop_front() { if (_data && _data->first) { - erase(_data->first); + remove(_data->first); } } -bool LinkedList::erase(ListElement *p_I) { +bool LinkedList::remove(ListElement *p_I) { if (_data) { bool ret = _data->erase(p_I); if (_data->size_cache == 0) { @@ -107,6 +107,22 @@ bool LinkedList::erase(ListElement *p_I) { return false; }; +ListElement *LinkedList::find(const Variant &p_value) { + ListElement *it = front(); + while (it) { + if (it->value == p_value) { + return it; + } + it = it->next(); + } + return nullptr; +} + +bool LinkedList::erase(const Variant &p_value) { + ListElement *I = find(p_value); + return remove(I); +} + void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); ClassDB::bind_method(D_METHOD("back"), &LinkedList::back); @@ -114,19 +130,31 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("pop_back"), &LinkedList::pop_back); ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); ClassDB::bind_method(D_METHOD("pop_front"), &LinkedList::pop_front); - ClassDB::bind_method(D_METHOD("erase", "element"), &LinkedList::erase); + + ClassDB::bind_method(D_METHOD("find", "value"), &LinkedList::find); + ClassDB::bind_method(D_METHOD("erase", "value"), &LinkedList::erase); + ClassDB::bind_method(D_METHOD("remove", "element"), &LinkedList::remove); + + ClassDB::bind_method(D_METHOD("empty"), &LinkedList::empty); + ClassDB::bind_method(D_METHOD("clear"), &LinkedList::clear); + ClassDB::bind_method(D_METHOD("size"), &LinkedList::size); } void LinkedList::clear() { while (front()) { - erase(front()); + remove(front()); } } void ListElement::_bind_methods() { + ClassDB::bind_method(D_METHOD("next"), &ListElement::next); + ClassDB::bind_method(D_METHOD("prev"), &ListElement::prev); + ClassDB::bind_method(D_METHOD("set_value", "value"), &ListElement::set_value); ClassDB::bind_method(D_METHOD("get_value"), &ListElement::get_value); + ClassDB::bind_method(D_METHOD("erase"), &ListElement::erase); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); } diff --git a/core/bind/list.h b/core/bind/list.h index 9d7db977..3fad9399 100644 --- a/core/bind/list.h +++ b/core/bind/list.h @@ -33,10 +33,12 @@ class ListElement : public Object { ListData *data = nullptr; public: - ListElement *next(); - ListElement *prev(); + ListElement *next() { return next_ptr; } + ListElement *prev() { return prev_ptr; } + Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } + void erase() { data->erase(this); } ListElement() {} @@ -62,13 +64,13 @@ class LinkedList : public Reference { ListElement *insert_after(ListElement *p_element, const Variant &p_value); ListElement *insert_before(ListElement *p_element, const Variant &p_value); - ListElement *find(const Variant &p_val); + ListElement *find(const Variant &p_value); - bool erase(ListElement *p_I); - // bool erase(const Variant &value); // TODO: rename to erase_first_found? - bool empty() const; + bool remove(ListElement *p_I); + bool erase(const Variant &value); + bool empty() const { return !_data || !_data->size_cache; } void clear(); - int size() const; + int size() const { return _data ? _data->size_cache : 0; } void swap(ListElement *p_A, ListElement *p_B); diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd index 009fbff4..5f27e288 100644 --- a/tests/project/goost/core/test_list.gd +++ b/tests/project/goost/core/test_list.gd @@ -1,23 +1,33 @@ extends "res://addons/gut/test.gd" +var list: LinkedList + + +func before_each(): + list = LinkedList.new() + + +func populate_test_data(list: LinkedList): + list.push_back("Goost") + list.push_back(37) + list.push_back(Color.blue) -func test_push_pop_back(): - var list = LinkedList.new() +func test_push_pop_back(): # Push back var n: ListElement n = list.push_back("Goost") assert_eq(n.value, "Goost") n = list.push_back(37) assert_eq(n.value, 37) - n = list.push_back(Color.red) - assert_eq(n.value, Color.red) + n = list.push_back(Color.blue) + assert_eq(n.value, Color.blue) # Pop back var v = null v = list.back().value list.pop_back() - assert_eq(v, Color.red) + assert_eq(v, Color.blue) v = list.back().value list.pop_back() assert_eq(v, 37) @@ -29,10 +39,106 @@ func test_push_pop_back(): assert_null(list.front()) +func test_push_pop_front(): + # Push front + var n: ListElement + n = list.push_front("Goost") + assert_eq(n.value, "Goost") + n = list.push_front(37) + assert_eq(n.value, 37) + n = list.push_front(Color.blue) + assert_eq(n.value, Color.blue) + + # Pop front + var v = null + v = list.front().value + list.pop_front() + assert_eq(v, Color.blue) + v = list.front().value + list.pop_front() + assert_eq(v, 37) + v = list.front().value + list.pop_front() + assert_eq(v, "Goost") + + assert_null(list.back()) + assert_null(list.front()) + + +func test_size(): + populate_test_data(list) + assert_eq(list.size(), 3) + + +func test_front(): + populate_test_data(list) + assert_eq(list.front().value, "Goost") + + +func test_back(): + populate_test_data(list) + assert_eq(list.back().value, Color.blue) + + +func test_find(): + populate_test_data(list) + assert_eq(list.find("Goost").value, "Goost") + + +func test_erase(): + populate_test_data(list) + var erased = list.erase(Color.blue) + assert_true(erased) + assert_eq(list.size(), 2) + erased = list.erase("does not exist") + assert_false(erased) + + +func test_remove(): + populate_test_data(list) + list.remove(list.find("Goost")) + assert_eq(list.size(), 2) + assert_null(list.find("Goost")) + + +func test_empty(): + populate_test_data(list) + var n: ListElement = list.front() + while n: + list.remove(n) + n = list.front() + assert_eq(list.size(), 0) + + +func test_clear(): + populate_test_data(list) + list.clear() + assert_eq(list.size(), 0) + assert_null(list.find("Goost")) + assert_null(list.find(37)) + assert_null(list.find(Color.blue)) + + +func test_next(): + populate_test_data(list) + var n = list.front() + assert_eq(n.value, "Goost") + n = n.next() + assert_eq(n.value, 37) + + +func test_prev(): + populate_test_data(list) + var n = list.back() + assert_eq(n.value, Color.blue) + n = n.prev() + assert_eq(n.value, 37) + + func test_cleanup(): - var list = LinkedList.new() + assert_eq(list.size(), 0) var n = list.push_back("Goost") + assert_eq(list.size(), 1) assert_not_null(n) - list.unreference() list = null assert_null(n) From 79f43eefa1da20d3a1a8174f4dc2dafa2642f5d0 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Fri, 11 Sep 2020 12:55:22 +0300 Subject: [PATCH 06/25] Move oneline implementations to declaration in linked list --- core/bind/list.cpp | 10 +--------- core/bind/list.h | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/core/bind/list.cpp b/core/bind/list.cpp index 64c05b70..aa2b3d87 100644 --- a/core/bind/list.cpp +++ b/core/bind/list.cpp @@ -22,14 +22,6 @@ bool ListData::erase(ListElement *p_I) { return true; } -ListElement *LinkedList::front() { - return _data ? _data->first : 0; -} - -ListElement *LinkedList::back() { - return _data ? _data->last : 0; -} - ListElement *LinkedList::push_back(const Variant &value) { if (!_data) { _data = memnew_allocator(ListData, DefaultAllocator); @@ -124,8 +116,8 @@ bool LinkedList::erase(const Variant &p_value) { } void LinkedList::_bind_methods() { - ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); ClassDB::bind_method(D_METHOD("back"), &LinkedList::back); + ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); ClassDB::bind_method(D_METHOD("push_back", "value"), &LinkedList::push_back); ClassDB::bind_method(D_METHOD("pop_back"), &LinkedList::pop_back); ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); diff --git a/core/bind/list.h b/core/bind/list.h index 3fad9399..9b4db2a6 100644 --- a/core/bind/list.h +++ b/core/bind/list.h @@ -54,8 +54,8 @@ class LinkedList : public Reference { ListData *_data = nullptr; public: - ListElement *front(); - ListElement *back(); + ListElement *back() { return _data ? _data->last : 0; } + ListElement *front() { return _data ? _data->first : 0; } ListElement *push_back(const Variant &value); void pop_back(); ListElement *push_front(const Variant &value); From 0250e51b0614897963c631965461fbda7902a05b Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Fri, 11 Sep 2020 13:02:05 +0300 Subject: [PATCH 07/25] Move linked list implementation to `types` directory --- core/SCsub | 2 +- core/register_core_types.cpp | 3 +-- core/{bind => types}/list.cpp | 0 core/{bind => types}/list.h | 0 4 files changed, 2 insertions(+), 3 deletions(-) rename core/{bind => types}/list.cpp (100%) rename core/{bind => types}/list.h (100%) diff --git a/core/SCsub b/core/SCsub index 50e73212..31f9a49e 100644 --- a/core/SCsub +++ b/core/SCsub @@ -9,4 +9,4 @@ if env["goost_math_enabled"]: SConscript("math/SCsub", exports="env_goost") env_goost.add_source_files(env.modules_sources, "*.cpp") -env_goost.add_source_files(env.modules_sources, "bind/*.cpp") +env_goost.add_source_files(env.modules_sources, "types/*.cpp") diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 6170edf2..e9ae9d7e 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -1,7 +1,6 @@ #include "register_core_types.h" -#include "core/bind/list.h" - +#include "types/list.h" #include "image/register_image_types.h" #include "math/register_math_types.h" diff --git a/core/bind/list.cpp b/core/types/list.cpp similarity index 100% rename from core/bind/list.cpp rename to core/types/list.cpp diff --git a/core/bind/list.h b/core/types/list.h similarity index 100% rename from core/bind/list.h rename to core/types/list.h From fae8809db914843d6491542ce4a1f669fe719ef0 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Fri, 11 Sep 2020 13:33:56 +0300 Subject: [PATCH 08/25] Port `List` move to back, front, before methods --- core/types/list.cpp | 75 +++++++++++++++++++++++++++ core/types/list.h | 6 +-- tests/project/goost/core/test_list.gd | 47 +++++++++++++---- 3 files changed, 113 insertions(+), 15 deletions(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index aa2b3d87..75bada2c 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -115,6 +115,77 @@ bool LinkedList::erase(const Variant &p_value) { return remove(I); } +void LinkedList::move_to_back(ListElement *p_I) { + ERR_FAIL_COND(p_I->data != _data); + if (!p_I->next_ptr) { + return; + } + if (_data->first == p_I) { + _data->first = p_I->next_ptr; + } + if (_data->last == p_I) { + _data->last = p_I->prev_ptr; + } + if (p_I->prev_ptr) { + p_I->prev_ptr->next_ptr = p_I->next_ptr; + } + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + + _data->last->next_ptr = p_I; + p_I->prev_ptr = _data->last; + p_I->next_ptr = nullptr; + _data->last = p_I; +} + +void LinkedList::move_to_front(ListElement *p_I) { + ERR_FAIL_COND(p_I->data != _data); + if (!p_I->prev_ptr) { + return; + } + if (_data->first == p_I) { + _data->first = p_I->next_ptr; + } + if (_data->last == p_I) { + _data->last = p_I->prev_ptr; + } + p_I->prev_ptr->next_ptr = p_I->next_ptr; + + if (p_I->next_ptr) { + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + } + _data->first->prev_ptr = p_I; + p_I->next_ptr = _data->first; + p_I->prev_ptr = nullptr; + _data->first = p_I; +} + +void LinkedList::move_before(ListElement *p_A, ListElement *p_B) { + if (p_A->prev_ptr) { + p_A->prev_ptr->next_ptr = p_A->next_ptr; + } else { + _data->first = p_A->next_ptr; + } + if (p_A->next_ptr) { + p_A->next_ptr->prev_ptr = p_A->prev_ptr; + } else { + _data->last = p_A->prev_ptr; + } + p_A->next_ptr = p_B; + if (!p_B) { + p_A->prev_ptr = _data->last; + _data->last = p_A; + return; + } + p_A->prev_ptr = p_B->prev_ptr; + + if (p_B->prev_ptr) { + p_B->prev_ptr->next_ptr = p_A; + } else { + _data->first = p_A; + } + p_B->prev_ptr = p_A; +} + void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("back"), &LinkedList::back); ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); @@ -130,6 +201,10 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("empty"), &LinkedList::empty); ClassDB::bind_method(D_METHOD("clear"), &LinkedList::clear); ClassDB::bind_method(D_METHOD("size"), &LinkedList::size); + + ClassDB::bind_method(D_METHOD("move_to_front"), &LinkedList::move_to_front); + ClassDB::bind_method(D_METHOD("move_to_back"), &LinkedList::move_to_back); + ClassDB::bind_method(D_METHOD("move_before"), &LinkedList::move_before); } void LinkedList::clear() { diff --git a/core/types/list.h b/core/types/list.h index 9b4db2a6..2ddec4ec 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -73,13 +73,11 @@ class LinkedList : public Reference { int size() const { return _data ? _data->size_cache : 0; } void swap(ListElement *p_A, ListElement *p_B); - - void move_to_back(ListElement *p_I); - void invert(); + void move_to_back(ListElement *p_I); void move_to_front(ListElement *p_I); - void move_before(ListElement *value, ListElement *where); + void move_before(ListElement *p_A, ListElement *p_B); void sort() { sort_custom>(); diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd index 5f27e288..48302ba4 100644 --- a/tests/project/goost/core/test_list.gd +++ b/tests/project/goost/core/test_list.gd @@ -8,9 +8,12 @@ func before_each(): func populate_test_data(list: LinkedList): - list.push_back("Goost") - list.push_back(37) - list.push_back(Color.blue) + var elements = [] + elements.append(list.push_back("Goost")) + elements.append(list.push_back(37)) + elements.append(list.push_back(Vector2.ONE)) + elements.append(list.push_back(Color.blue)) + return elements func test_push_pop_back(): @@ -66,8 +69,9 @@ func test_push_pop_front(): func test_size(): - populate_test_data(list) - assert_eq(list.size(), 3) + var elements = populate_test_data(list) + var original_size = elements.size() + assert_eq(list.size(), original_size) func test_front(): @@ -86,20 +90,22 @@ func test_find(): func test_erase(): - populate_test_data(list) + var elements = populate_test_data(list) + var original_size = elements.size() var erased = list.erase(Color.blue) assert_true(erased) - assert_eq(list.size(), 2) + assert_eq(list.size(), original_size - 1) erased = list.erase("does not exist") assert_false(erased) func test_remove(): - populate_test_data(list) + var elements = populate_test_data(list) + var original_size = elements.size() list.remove(list.find("Goost")) - assert_eq(list.size(), 2) + assert_eq(list.size(), original_size - 1) assert_null(list.find("Goost")) - + func test_empty(): populate_test_data(list) @@ -132,7 +138,26 @@ func test_prev(): var n = list.back() assert_eq(n.value, Color.blue) n = n.prev() - assert_eq(n.value, 37) + assert_eq(n.value, Vector2.ONE) + + +func test_move_to_front(): + var elements = populate_test_data(list) + list.move_to_front(elements[2]) + assert_eq(list.front().value, Vector2.ONE) + + +func test_move_to_back(): + var elements = populate_test_data(list) + list.move_to_back(elements[0]) + assert_eq(list.back().value, "Goost") + + +func test_move_before(): + var elements = populate_test_data(list) + assert_eq(elements[3].value, Color.blue) + list.move_before(elements[3], elements[1]) + assert_eq(list.front().next().value, elements[3].value) func test_cleanup(): From 4a38bc3d2bc5701a2a443d91fa477569f10126f2 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 16:49:53 +0300 Subject: [PATCH 09/25] Port and fix linked list `swap`, add `invert` methods This took some time to get right (hopefully). The tests should cover the swap functionality which is currently broken in Godot. --- core/types/list.cpp | 63 +++- tests/project/.gutconfig.json | 1 + tests/project/goost/core/test_list.gd | 169 --------- tests/project/goost/core/types/test_list.gd | 385 ++++++++++++++++++++ 4 files changed, 444 insertions(+), 174 deletions(-) delete mode 100644 tests/project/goost/core/test_list.gd create mode 100644 tests/project/goost/core/types/test_list.gd diff --git a/core/types/list.cpp b/core/types/list.cpp index 75bada2c..8f139373 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -24,7 +24,7 @@ bool ListData::erase(ListElement *p_I) { ListElement *LinkedList::push_back(const Variant &value) { if (!_data) { - _data = memnew_allocator(ListData, DefaultAllocator); + _data = memnew(ListData); _data->first = nullptr; _data->last = nullptr; _data->size_cache = 0; @@ -57,12 +57,12 @@ void LinkedList::pop_back() { ListElement *LinkedList::push_front(const Variant &value) { if (!_data) { - _data = memnew_allocator(ListData, DefaultAllocator); + _data = memnew(ListData); _data->first = nullptr; _data->last = nullptr; _data->size_cache = 0; } - ListElement *n = memnew_allocator(ListElement, DefaultAllocator); + ListElement *n = memnew(ListElement); n->value = value; n->prev_ptr = 0; n->next_ptr = _data->first; @@ -91,7 +91,7 @@ bool LinkedList::remove(ListElement *p_I) { if (_data) { bool ret = _data->erase(p_I); if (_data->size_cache == 0) { - memdelete_allocator(_data); + memdelete(_data); _data = nullptr; } return ret; @@ -115,6 +115,56 @@ bool LinkedList::erase(const Variant &p_value) { return remove(I); } +void LinkedList::swap(ListElement *p_A, ListElement *p_B) { + ERR_FAIL_COND(!p_A || !p_B); + ERR_FAIL_COND(p_A->data != _data); + ERR_FAIL_COND(p_B->data != _data); + + if (p_A == p_B) { + return; + } + ListElement *A_prev = p_A->prev_ptr; + ListElement *A_next = p_A->next_ptr; + ListElement *B_prev = p_B->prev_ptr; + ListElement *B_next = p_B->next_ptr; + + if (A_prev) { + A_prev->next_ptr = p_B; + } else { + _data->first = p_B; + } + if (B_prev) { + B_prev->next_ptr = p_A; + } else { + _data->first = p_A; + } + if (A_next) { + A_next->prev_ptr = p_B; + } else { + _data->last = p_B; + } + if (B_next) { + B_next->prev_ptr = p_A; + } else { + _data->last = p_A; + } + p_A->prev_ptr = A_next == p_B ? p_B : B_prev; + p_A->next_ptr = B_next == p_A ? p_B : B_next; + p_B->prev_ptr = B_next == p_A ? p_A : A_prev; + p_B->next_ptr = A_next == p_B ? p_A : A_next; +} + +void LinkedList::invert() { + int s = size() / 2; + ListElement *F = front(); + ListElement *B = back(); + for (int i = 0; i < s; i++) { + SWAP(F->value, B->value); + F = F->next(); + B = B->prev(); + } +} + void LinkedList::move_to_back(ListElement *p_I) { ERR_FAIL_COND(p_I->data != _data); if (!p_I->next_ptr) { @@ -202,6 +252,9 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &LinkedList::clear); ClassDB::bind_method(D_METHOD("size"), &LinkedList::size); + ClassDB::bind_method(D_METHOD("swap"), &LinkedList::swap); + ClassDB::bind_method(D_METHOD("invert"), &LinkedList::invert); + ClassDB::bind_method(D_METHOD("move_to_front"), &LinkedList::move_to_front); ClassDB::bind_method(D_METHOD("move_to_back"), &LinkedList::move_to_back); ClassDB::bind_method(D_METHOD("move_before"), &LinkedList::move_before); @@ -229,6 +282,6 @@ LinkedList::~LinkedList() { clear(); if (_data) { ERR_FAIL_COND(_data->size_cache); - memdelete_allocator(_data); + memdelete(_data); } } diff --git a/tests/project/.gutconfig.json b/tests/project/.gutconfig.json index 06de4e20..48972a98 100644 --- a/tests/project/.gutconfig.json +++ b/tests/project/.gutconfig.json @@ -1,5 +1,6 @@ { "should_exit_on_success":true, + "should_maximize": true, "include_subdirs":true, "dirs":[ "res://goost/" diff --git a/tests/project/goost/core/test_list.gd b/tests/project/goost/core/test_list.gd deleted file mode 100644 index 48302ba4..00000000 --- a/tests/project/goost/core/test_list.gd +++ /dev/null @@ -1,169 +0,0 @@ -extends "res://addons/gut/test.gd" - -var list: LinkedList - - -func before_each(): - list = LinkedList.new() - - -func populate_test_data(list: LinkedList): - var elements = [] - elements.append(list.push_back("Goost")) - elements.append(list.push_back(37)) - elements.append(list.push_back(Vector2.ONE)) - elements.append(list.push_back(Color.blue)) - return elements - - -func test_push_pop_back(): - # Push back - var n: ListElement - n = list.push_back("Goost") - assert_eq(n.value, "Goost") - n = list.push_back(37) - assert_eq(n.value, 37) - n = list.push_back(Color.blue) - assert_eq(n.value, Color.blue) - - # Pop back - var v = null - v = list.back().value - list.pop_back() - assert_eq(v, Color.blue) - v = list.back().value - list.pop_back() - assert_eq(v, 37) - v = list.back().value - list.pop_back() - assert_eq(v, "Goost") - - assert_null(list.back()) - assert_null(list.front()) - - -func test_push_pop_front(): - # Push front - var n: ListElement - n = list.push_front("Goost") - assert_eq(n.value, "Goost") - n = list.push_front(37) - assert_eq(n.value, 37) - n = list.push_front(Color.blue) - assert_eq(n.value, Color.blue) - - # Pop front - var v = null - v = list.front().value - list.pop_front() - assert_eq(v, Color.blue) - v = list.front().value - list.pop_front() - assert_eq(v, 37) - v = list.front().value - list.pop_front() - assert_eq(v, "Goost") - - assert_null(list.back()) - assert_null(list.front()) - - -func test_size(): - var elements = populate_test_data(list) - var original_size = elements.size() - assert_eq(list.size(), original_size) - - -func test_front(): - populate_test_data(list) - assert_eq(list.front().value, "Goost") - - -func test_back(): - populate_test_data(list) - assert_eq(list.back().value, Color.blue) - - -func test_find(): - populate_test_data(list) - assert_eq(list.find("Goost").value, "Goost") - - -func test_erase(): - var elements = populate_test_data(list) - var original_size = elements.size() - var erased = list.erase(Color.blue) - assert_true(erased) - assert_eq(list.size(), original_size - 1) - erased = list.erase("does not exist") - assert_false(erased) - - -func test_remove(): - var elements = populate_test_data(list) - var original_size = elements.size() - list.remove(list.find("Goost")) - assert_eq(list.size(), original_size - 1) - assert_null(list.find("Goost")) - - -func test_empty(): - populate_test_data(list) - var n: ListElement = list.front() - while n: - list.remove(n) - n = list.front() - assert_eq(list.size(), 0) - - -func test_clear(): - populate_test_data(list) - list.clear() - assert_eq(list.size(), 0) - assert_null(list.find("Goost")) - assert_null(list.find(37)) - assert_null(list.find(Color.blue)) - - -func test_next(): - populate_test_data(list) - var n = list.front() - assert_eq(n.value, "Goost") - n = n.next() - assert_eq(n.value, 37) - - -func test_prev(): - populate_test_data(list) - var n = list.back() - assert_eq(n.value, Color.blue) - n = n.prev() - assert_eq(n.value, Vector2.ONE) - - -func test_move_to_front(): - var elements = populate_test_data(list) - list.move_to_front(elements[2]) - assert_eq(list.front().value, Vector2.ONE) - - -func test_move_to_back(): - var elements = populate_test_data(list) - list.move_to_back(elements[0]) - assert_eq(list.back().value, "Goost") - - -func test_move_before(): - var elements = populate_test_data(list) - assert_eq(elements[3].value, Color.blue) - list.move_before(elements[3], elements[1]) - assert_eq(list.front().next().value, elements[3].value) - - -func test_cleanup(): - assert_eq(list.size(), 0) - var n = list.push_back("Goost") - assert_eq(list.size(), 1) - assert_not_null(n) - list = null - assert_null(n) diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd new file mode 100644 index 00000000..c28a306d --- /dev/null +++ b/tests/project/goost/core/types/test_list.gd @@ -0,0 +1,385 @@ +extends "res://addons/gut/test.gd" + +var list: LinkedList + + +func before_each(): + list = LinkedList.new() + + +func populate_test_data(p_list: LinkedList): + var elements = [] + elements.append(p_list.push_back("Goost")) + elements.append(p_list.push_back(37)) + elements.append(p_list.push_back(Vector2.ONE)) + elements.append(p_list.push_back(Color.blue)) + return elements + + +func populate_test_data_integers(p_list: LinkedList, p_num_elements: int): + var elements = [] + for i in p_num_elements: + elements.append(p_list.push_back(i)) + return elements + + +func test_push_pop_back(): + # Push back + var n: ListElement + n = list.push_back("Goost") + assert_eq(n.value, "Goost") + n = list.push_back(37) + assert_eq(n.value, 37) + n = list.push_back(Color.blue) + assert_eq(n.value, Color.blue) + + # Pop back + var v = null + v = list.back().value + list.pop_back() + assert_eq(v, Color.blue) + v = list.back().value + list.pop_back() + assert_eq(v, 37) + v = list.back().value + list.pop_back() + assert_eq(v, "Goost") + + assert_null(list.back()) + assert_null(list.front()) + + +func test_push_pop_front(): + # Push front + var n: ListElement + n = list.push_front("Goost") + assert_eq(n.value, "Goost") + n = list.push_front(37) + assert_eq(n.value, 37) + n = list.push_front(Color.blue) + assert_eq(n.value, Color.blue) + + # Pop front + var v = null + v = list.front().value + list.pop_front() + assert_eq(v, Color.blue) + v = list.front().value + list.pop_front() + assert_eq(v, 37) + v = list.front().value + list.pop_front() + assert_eq(v, "Goost") + + assert_null(list.back()) + assert_null(list.front()) + + +func test_size(): + var elements = populate_test_data(list) + var original_size = elements.size() + assert_eq(list.size(), original_size) + + +func test_front(): + populate_test_data(list) + assert_eq(list.front().value, "Goost") + + +func test_back(): + populate_test_data(list) + assert_eq(list.back().value, Color.blue) + + +func test_find(): + populate_test_data(list) + assert_eq(list.find("Goost").value, "Goost") + + +func test_erase(): + var elements = populate_test_data(list) + var original_size = elements.size() + var erased = list.erase(Color.blue) + assert_true(erased) + assert_eq(list.size(), original_size - 1) + erased = list.erase("does not exist") + assert_false(erased) + + +func test_remove(): + var elements = populate_test_data(list) + var original_size = elements.size() + var removed = list.remove(list.find("Goost")) + assert_true(removed) + assert_eq(list.size(), original_size - 1) + assert_null(list.find("Goost")) + + +func test_empty(): + populate_test_data(list) + var n: ListElement = list.front() + while n: + var removed = list.remove(n) + assert_true(removed) + n = list.front() + assert_eq(list.size(), 0) + + +func test_clear(): + populate_test_data(list) + list.clear() + assert_eq(list.size(), 0) + assert_null(list.find("Goost")) + assert_null(list.find(37)) + assert_null(list.find(Color.blue)) + + +func test_next(): + populate_test_data(list) + var n = list.front() + assert_eq(n.value, "Goost") + n = n.next() + assert_eq(n.value, 37) + + +func test_prev(): + populate_test_data(list) + var n = list.back() + assert_eq(n.value, Color.blue) + n = n.prev() + assert_eq(n.value, Vector2.ONE) + + +func test_swap_adjacent_front_and_back(): + var nodes = populate_test_data_integers(list, 2) + list.swap(nodes[0], nodes[1]) + + assert_null(list.front().prev()) + assert_ne(list.front(), list.front().next()) + + assert_eq(list.front(), nodes[1]) + assert_eq(list.back(), nodes[0]) + + assert_null(list.back().next()) + assert_ne(list.back(), list.back().prev()) + + +func test_swap_0_1__first_adjacent_pair(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[0], nodes[1]) + + assert_null(list.front().prev()) + assert_ne(list.front(), list.front().next()) + + assert_eq(list.front(), nodes[1]) + assert_eq(list.front().next(), nodes[0]) + assert_eq(list.back().prev(), nodes[2]) + assert_eq(list.back(), nodes[3]) + + assert_null(list.back().next()) + assert_ne(list.back(), list.back().prev()) + + +func test_swap_1_2__middle_adjacent_pair(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[1], nodes[2]) + + assert_null(list.front().prev()) + + assert_eq(list.front(), nodes[0]) + assert_eq(list.front().next(), nodes[2]) + assert_eq(list.back().prev(), nodes[1]) + assert_eq(list.back(), nodes[3]) + + assert_null(list.back().next()) + + +func test_swap_2_3__last_adjacent_pair(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[2], nodes[3]) + + assert_null(list.front().prev()) + + assert_eq(list.front(), nodes[0]) + assert_eq(list.front().next(), nodes[1]) + assert_eq(list.back().prev(), nodes[3]) + assert_eq(list.back(), nodes[2]) + + assert_null(list.back().next()) + + +func test_swap_0_2__first_cross_pair(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[0], nodes[2]) + + assert_null(list.front().prev()) + + assert_eq(list.front(), nodes[2]) + assert_eq(list.front().next(), nodes[1]) + assert_eq(list.back().prev(), nodes[0]) + assert_eq(list.back(), nodes[3]) + + assert_null(list.back().next()) + + +func test_swap_1_3__last_cross_pair(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[1], nodes[3]) + + assert_null(list.front().prev()) + + assert_eq(list.front(), nodes[0]) + assert_eq(list.front().next(), nodes[3]) + assert_eq(list.back().prev(), nodes[2]) + assert_eq(list.back(), nodes[1]) + + assert_null(list.back().next()) + + +func test_swap_0_3__front_and_back(): + var nodes = populate_test_data_integers(list, 4) + list.swap(nodes[0], nodes[3]) + + assert_null(list.front().prev()) + + assert_eq(list.front(), nodes[3]) + assert_eq(list.front().next(), nodes[1]) + assert_eq(list.back().prev(), nodes[2]) + assert_eq(list.back(), nodes[0]) + + assert_null(list.back().next()) + + +func test_swap_adjacent_front_back_reverse(): + var nodes = populate_test_data_integers(list, 2) + list.swap(nodes[1], nodes[0]) + var it = list.front() + while it: + var prev_it = it + it = it.next() + if it == prev_it: + assert_true(false, "Infinite loop detected.") + break + + +func test_swap_adjacent_middle_reverse(): + var nodes = populate_test_data_integers(list, 10) + list.swap(nodes[5], nodes[4]) + var it = list.front() + while it: + var prev_it = it + it = it.next() + if it == prev_it: + assert_true(false, "Infinite loop detected.") + break + + +func test_swap_random(): + var size = 100 + var nodes = populate_test_data_integers(list, size) + assert_eq(nodes.size(), size) + seed(0) + for _i in 1000: + var a = nodes[randi() % size] + var b = nodes[randi() % size] + var va = a.value + var vb = b.value + list.swap(a, b) + + var num_elements = 0 + + # Fully traversable after swap? + var it = list.front() + while it: + num_elements += 1 + var prev_it = it + it = it.next() + if it == prev_it: + assert_true(false, "Infinite loop detected.") + break + + # Even if traversed, + # we should not lose anything in the process. + if num_elements != size: + assert_true(false, "Element count mismatch.") + break + + assert_eq(va, a.value) + assert_eq(vb, b.value) + + +func test_swap_front_and_back_2_elements(): + var a = list.push_back("Goost") + var b = list.push_back(Color.blue) + + list.swap(a, b) + + assert_eq(list.front().value, Color.blue) + assert_eq(list.back().value, "Goost") + + +func test_swap_front_and_back_3_elements(): + var a = list.push_back("Goost") + var _b = list.push_back(37) + var c = list.push_back(Vector2.ONE) + + list.swap(a, c) + + assert_eq(list.front().value, Vector2.ONE) + assert_eq(list.back().value, "Goost") + + +func test_swap_front_and_back_4_elements(): + var a = list.push_back("Goost") + var _b = list.push_back(37) + var _c = list.push_back(Vector2.ONE) + var d = list.push_back(Color.blue) + + list.swap(a, d) + + assert_eq(list.front().value, Color.blue) + assert_eq(list.back().value, "Goost") + + +func test_swap_self(): + var a = list.push_back("Goost") + list.swap(a, a) + assert_eq(list.front().value, "Goost") + assert_eq(list.back().value, "Goost") + + +func test_invert(): + populate_test_data(list) + list.invert() + assert_eq(list.front().value, Color.blue) + assert_eq(list.front().next().value, Vector2.ONE) + assert_eq(list.back().prev().value, 37) + assert_eq(list.back().value, "Goost") + + +func test_move_to_front(): + var elements = populate_test_data(list) + list.move_to_front(elements[2]) + assert_eq(list.front().value, Vector2.ONE) + + +func test_move_to_back(): + var elements = populate_test_data(list) + list.move_to_back(elements[0]) + assert_eq(list.back().value, "Goost") + + +func test_move_before(): + var elements = populate_test_data(list) + assert_eq(elements[3].value, Color.blue) + list.move_before(elements[3], elements[1]) + assert_eq(list.front().next().value, elements[3].value) + + +func test_cleanup(): + assert_eq(list.size(), 0) + var n = list.push_back("Goost") + assert_eq(list.size(), 1) + assert_not_null(n) + list = null + assert_null(n) From 39625a64694ae45ae2b9cfeaa39045c0bc8ea890 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 19:20:47 +0300 Subject: [PATCH 10/25] Add properties for linked list `front`, `back`, `next`, `prev` Had to rename underlying methods because they would conflict with property names. In Godot 4.0, it would also fail the tests. It's recommended to use properties in GDScript for this. In other langs, you'd simply use corresponding linked list data structure built-in anyway. --- core/types/list.cpp | 30 ++-- core/types/list.h | 11 +- tests/project/goost/core/types/test_list.gd | 168 ++++++++++---------- 3 files changed, 108 insertions(+), 101 deletions(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 8f139373..008c0f5b 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -100,12 +100,12 @@ bool LinkedList::remove(ListElement *p_I) { }; ListElement *LinkedList::find(const Variant &p_value) { - ListElement *it = front(); + ListElement *it = get_front(); while (it) { if (it->value == p_value) { return it; } - it = it->next(); + it = it->get_next(); } return nullptr; } @@ -156,12 +156,12 @@ void LinkedList::swap(ListElement *p_A, ListElement *p_B) { void LinkedList::invert() { int s = size() / 2; - ListElement *F = front(); - ListElement *B = back(); + ListElement *F = get_front(); + ListElement *B = get_back(); for (int i = 0; i < s; i++) { SWAP(F->value, B->value); - F = F->next(); - B = B->prev(); + F = F->get_next(); + B = B->get_prev(); } } @@ -237,8 +237,9 @@ void LinkedList::move_before(ListElement *p_A, ListElement *p_B) { } void LinkedList::_bind_methods() { - ClassDB::bind_method(D_METHOD("back"), &LinkedList::back); - ClassDB::bind_method(D_METHOD("front"), &LinkedList::front); + ClassDB::bind_method(D_METHOD("get_front"), &LinkedList::get_front); + ClassDB::bind_method(D_METHOD("get_back"), &LinkedList::get_back); + ClassDB::bind_method(D_METHOD("push_back", "value"), &LinkedList::push_back); ClassDB::bind_method(D_METHOD("pop_back"), &LinkedList::pop_back); ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); @@ -258,17 +259,20 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("move_to_front"), &LinkedList::move_to_front); ClassDB::bind_method(D_METHOD("move_to_back"), &LinkedList::move_to_back); ClassDB::bind_method(D_METHOD("move_before"), &LinkedList::move_before); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); } void LinkedList::clear() { - while (front()) { - remove(front()); + while (get_front()) { + remove(get_front()); } } void ListElement::_bind_methods() { - ClassDB::bind_method(D_METHOD("next"), &ListElement::next); - ClassDB::bind_method(D_METHOD("prev"), &ListElement::prev); + ClassDB::bind_method(D_METHOD("get_next"), &ListElement::get_next); + ClassDB::bind_method(D_METHOD("get_prev"), &ListElement::get_prev); ClassDB::bind_method(D_METHOD("set_value", "value"), &ListElement::set_value); ClassDB::bind_method(D_METHOD("get_value"), &ListElement::get_value); @@ -276,6 +280,8 @@ void ListElement::_bind_methods() { ClassDB::bind_method(D_METHOD("erase"), &ListElement::erase); ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "next"), "", "get_next"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "prev"), "", "get_prev"); } LinkedList::~LinkedList() { diff --git a/core/types/list.h b/core/types/list.h index 2ddec4ec..18488d43 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -33,8 +33,8 @@ class ListElement : public Object { ListData *data = nullptr; public: - ListElement *next() { return next_ptr; } - ListElement *prev() { return prev_ptr; } + ListElement *get_next() { return next_ptr; } + ListElement *get_prev() { return prev_ptr; } Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } @@ -54,8 +54,9 @@ class LinkedList : public Reference { ListData *_data = nullptr; public: - ListElement *back() { return _data ? _data->last : 0; } - ListElement *front() { return _data ? _data->first : 0; } + ListElement *get_front() { return _data ? _data->first : 0; } + ListElement *get_back() { return _data ? _data->last : 0; } + ListElement *push_back(const Variant &value); void pop_back(); ListElement *push_front(const Variant &value); @@ -144,7 +145,7 @@ class LinkedList : public Reference { ListElement **aux_buffer = memnew_arr(ListElement *, s); int idx = 0; - for (ListElement *E = front(); E; E = E->next_ptr) { + for (ListElement *E = get_front(); E; E = E->next_ptr) { aux_buffer[idx] = E; idx++; } diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index c28a306d..3ff262e6 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -35,18 +35,18 @@ func test_push_pop_back(): # Pop back var v = null - v = list.back().value + v = list.back.value list.pop_back() assert_eq(v, Color.blue) - v = list.back().value + v = list.back.value list.pop_back() assert_eq(v, 37) - v = list.back().value + v = list.back.value list.pop_back() assert_eq(v, "Goost") - assert_null(list.back()) - assert_null(list.front()) + assert_null(list.back) + assert_null(list.front) func test_push_pop_front(): @@ -61,18 +61,18 @@ func test_push_pop_front(): # Pop front var v = null - v = list.front().value + v = list.front.value list.pop_front() assert_eq(v, Color.blue) - v = list.front().value + v = list.front.value list.pop_front() assert_eq(v, 37) - v = list.front().value + v = list.front.value list.pop_front() assert_eq(v, "Goost") - assert_null(list.back()) - assert_null(list.front()) + assert_null(list.back) + assert_null(list.front) func test_size(): @@ -83,12 +83,12 @@ func test_size(): func test_front(): populate_test_data(list) - assert_eq(list.front().value, "Goost") + assert_eq(list.front.value, "Goost") func test_back(): populate_test_data(list) - assert_eq(list.back().value, Color.blue) + assert_eq(list.back.value, Color.blue) func test_find(): @@ -117,11 +117,11 @@ func test_remove(): func test_empty(): populate_test_data(list) - var n: ListElement = list.front() + var n: ListElement = list.front while n: var removed = list.remove(n) assert_true(removed) - n = list.front() + n = list.front assert_eq(list.size(), 0) @@ -136,17 +136,17 @@ func test_clear(): func test_next(): populate_test_data(list) - var n = list.front() + var n = list.front assert_eq(n.value, "Goost") - n = n.next() + n = n.next assert_eq(n.value, 37) func test_prev(): populate_test_data(list) - var n = list.back() + var n = list.back assert_eq(n.value, Color.blue) - n = n.prev() + n = n.prev assert_eq(n.value, Vector2.ONE) @@ -154,109 +154,109 @@ func test_swap_adjacent_front_and_back(): var nodes = populate_test_data_integers(list, 2) list.swap(nodes[0], nodes[1]) - assert_null(list.front().prev()) - assert_ne(list.front(), list.front().next()) + assert_null(list.front.prev) + assert_ne(list.front, list.front.next) - assert_eq(list.front(), nodes[1]) - assert_eq(list.back(), nodes[0]) + assert_eq(list.front, nodes[1]) + assert_eq(list.back, nodes[0]) - assert_null(list.back().next()) - assert_ne(list.back(), list.back().prev()) + assert_null(list.back.next) + assert_ne(list.back, list.back.prev) func test_swap_0_1__first_adjacent_pair(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[0], nodes[1]) - assert_null(list.front().prev()) - assert_ne(list.front(), list.front().next()) + assert_null(list.front.prev) + assert_ne(list.front, list.front.next) - assert_eq(list.front(), nodes[1]) - assert_eq(list.front().next(), nodes[0]) - assert_eq(list.back().prev(), nodes[2]) - assert_eq(list.back(), nodes[3]) + assert_eq(list.front, nodes[1]) + assert_eq(list.front.next, nodes[0]) + assert_eq(list.back.prev, nodes[2]) + assert_eq(list.back, nodes[3]) - assert_null(list.back().next()) - assert_ne(list.back(), list.back().prev()) + assert_null(list.back.next) + assert_ne(list.back, list.back.prev) func test_swap_1_2__middle_adjacent_pair(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[1], nodes[2]) - assert_null(list.front().prev()) + assert_null(list.front.prev) - assert_eq(list.front(), nodes[0]) - assert_eq(list.front().next(), nodes[2]) - assert_eq(list.back().prev(), nodes[1]) - assert_eq(list.back(), nodes[3]) + assert_eq(list.front, nodes[0]) + assert_eq(list.front.next, nodes[2]) + assert_eq(list.back.prev, nodes[1]) + assert_eq(list.back, nodes[3]) - assert_null(list.back().next()) + assert_null(list.back.next) func test_swap_2_3__last_adjacent_pair(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[2], nodes[3]) - assert_null(list.front().prev()) + assert_null(list.front.prev) - assert_eq(list.front(), nodes[0]) - assert_eq(list.front().next(), nodes[1]) - assert_eq(list.back().prev(), nodes[3]) - assert_eq(list.back(), nodes[2]) + assert_eq(list.front, nodes[0]) + assert_eq(list.front.next, nodes[1]) + assert_eq(list.back.prev, nodes[3]) + assert_eq(list.back, nodes[2]) - assert_null(list.back().next()) + assert_null(list.back.next) func test_swap_0_2__first_cross_pair(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[0], nodes[2]) - assert_null(list.front().prev()) + assert_null(list.front.prev) - assert_eq(list.front(), nodes[2]) - assert_eq(list.front().next(), nodes[1]) - assert_eq(list.back().prev(), nodes[0]) - assert_eq(list.back(), nodes[3]) + assert_eq(list.front, nodes[2]) + assert_eq(list.front.next, nodes[1]) + assert_eq(list.back.prev, nodes[0]) + assert_eq(list.back, nodes[3]) - assert_null(list.back().next()) + assert_null(list.back.next) func test_swap_1_3__last_cross_pair(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[1], nodes[3]) - assert_null(list.front().prev()) + assert_null(list.front.prev) - assert_eq(list.front(), nodes[0]) - assert_eq(list.front().next(), nodes[3]) - assert_eq(list.back().prev(), nodes[2]) - assert_eq(list.back(), nodes[1]) + assert_eq(list.front, nodes[0]) + assert_eq(list.front.next, nodes[3]) + assert_eq(list.back.prev, nodes[2]) + assert_eq(list.back, nodes[1]) - assert_null(list.back().next()) + assert_null(list.back.next) func test_swap_0_3__front_and_back(): var nodes = populate_test_data_integers(list, 4) list.swap(nodes[0], nodes[3]) - assert_null(list.front().prev()) + assert_null(list.front.prev) - assert_eq(list.front(), nodes[3]) - assert_eq(list.front().next(), nodes[1]) - assert_eq(list.back().prev(), nodes[2]) - assert_eq(list.back(), nodes[0]) + assert_eq(list.front, nodes[3]) + assert_eq(list.front.next, nodes[1]) + assert_eq(list.back.prev, nodes[2]) + assert_eq(list.back, nodes[0]) - assert_null(list.back().next()) + assert_null(list.back.next) func test_swap_adjacent_front_back_reverse(): var nodes = populate_test_data_integers(list, 2) list.swap(nodes[1], nodes[0]) - var it = list.front() + var it = list.front while it: var prev_it = it - it = it.next() + it = it.next if it == prev_it: assert_true(false, "Infinite loop detected.") break @@ -265,10 +265,10 @@ func test_swap_adjacent_front_back_reverse(): func test_swap_adjacent_middle_reverse(): var nodes = populate_test_data_integers(list, 10) list.swap(nodes[5], nodes[4]) - var it = list.front() + var it = list.front while it: var prev_it = it - it = it.next() + it = it.next if it == prev_it: assert_true(false, "Infinite loop detected.") break @@ -289,16 +289,16 @@ func test_swap_random(): var num_elements = 0 # Fully traversable after swap? - var it = list.front() + var it = list.front while it: num_elements += 1 var prev_it = it - it = it.next() + it = it.next if it == prev_it: assert_true(false, "Infinite loop detected.") break - # Even if traversed, + # Even if traversed, # we should not lose anything in the process. if num_elements != size: assert_true(false, "Element count mismatch.") @@ -314,8 +314,8 @@ func test_swap_front_and_back_2_elements(): list.swap(a, b) - assert_eq(list.front().value, Color.blue) - assert_eq(list.back().value, "Goost") + assert_eq(list.front.value, Color.blue) + assert_eq(list.back.value, "Goost") func test_swap_front_and_back_3_elements(): @@ -325,8 +325,8 @@ func test_swap_front_and_back_3_elements(): list.swap(a, c) - assert_eq(list.front().value, Vector2.ONE) - assert_eq(list.back().value, "Goost") + assert_eq(list.front.value, Vector2.ONE) + assert_eq(list.back.value, "Goost") func test_swap_front_and_back_4_elements(): @@ -337,43 +337,43 @@ func test_swap_front_and_back_4_elements(): list.swap(a, d) - assert_eq(list.front().value, Color.blue) - assert_eq(list.back().value, "Goost") + assert_eq(list.front.value, Color.blue) + assert_eq(list.back.value, "Goost") func test_swap_self(): var a = list.push_back("Goost") list.swap(a, a) - assert_eq(list.front().value, "Goost") - assert_eq(list.back().value, "Goost") + assert_eq(list.front.value, "Goost") + assert_eq(list.back.value, "Goost") func test_invert(): populate_test_data(list) list.invert() - assert_eq(list.front().value, Color.blue) - assert_eq(list.front().next().value, Vector2.ONE) - assert_eq(list.back().prev().value, 37) - assert_eq(list.back().value, "Goost") + assert_eq(list.front.value, Color.blue) + assert_eq(list.front.next.value, Vector2.ONE) + assert_eq(list.back.prev.value, 37) + assert_eq(list.back.value, "Goost") func test_move_to_front(): var elements = populate_test_data(list) list.move_to_front(elements[2]) - assert_eq(list.front().value, Vector2.ONE) + assert_eq(list.front.value, Vector2.ONE) func test_move_to_back(): var elements = populate_test_data(list) list.move_to_back(elements[0]) - assert_eq(list.back().value, "Goost") + assert_eq(list.back.value, "Goost") func test_move_before(): var elements = populate_test_data(list) assert_eq(elements[3].value, Color.blue) list.move_before(elements[3], elements[1]) - assert_eq(list.front().next().value, elements[3].value) + assert_eq(list.front.next.value, elements[3].value) func test_cleanup(): From b7285aeb077a917c89eb3859042a94331d96680c Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 19:25:46 +0300 Subject: [PATCH 11/25] Fix missing rename for `front()` in linked list --- core/types/list.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/list.h b/core/types/list.h index 18488d43..438d92c7 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -89,7 +89,7 @@ class LinkedList : public Reference { if (size() < 2) return; - ListElement *from = front(); + ListElement *from = get_front(); ListElement *current = from; ListElement *to = from; From 91683973186e83d3ffb405ee21860c0a2c2394da Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 19:31:45 +0300 Subject: [PATCH 12/25] Do not print error on `List::remove()` if can't find a value For consistency with godotengine/godot#34028. Note that `erase` was renamed to `remove` in Goost. --- core/types/list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 008c0f5b..3e7b573c 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -88,7 +88,7 @@ void LinkedList::pop_front() { } bool LinkedList::remove(ListElement *p_I) { - if (_data) { + if (_data && p_I) { bool ret = _data->erase(p_I); if (_data->size_cache == 0) { memdelete(_data); From 7e8e5366ea85d592eddb9526087f5d4f0e6a1ba9 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 19:49:25 +0300 Subject: [PATCH 13/25] Port linked list `insert_after` and `insert_before` --- core/types/list.cpp | 51 +++++++++++++++++++++ tests/project/goost/core/types/test_list.gd | 26 +++++++++++ 2 files changed, 77 insertions(+) diff --git a/core/types/list.cpp b/core/types/list.cpp index 3e7b573c..1e250259 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -87,6 +87,54 @@ void LinkedList::pop_front() { } } +ListElement *LinkedList::insert_after(ListElement *p_element, const Variant &p_value) { + CRASH_COND(p_element && (!_data || p_element->data != _data)); + + if (!p_element) { + return push_back(p_value); + } + ListElement *n = memnew(ListElement); + n->value = p_value; + n->prev_ptr = p_element; + n->next_ptr = p_element->next_ptr; + n->data = _data; + + if (!p_element->next_ptr) { + _data->last = n; + } else { + p_element->next_ptr->prev_ptr = n; + } + p_element->next_ptr = n; + + _data->size_cache++; + + return n; +} + +ListElement *LinkedList::insert_before(ListElement *p_element, const Variant &p_value) { + CRASH_COND(p_element && (!_data || p_element->data != _data)); + + if (!p_element) { + return push_back(p_value); + } + ListElement *n = memnew(ListElement); + n->value = p_value; + n->prev_ptr = p_element->prev_ptr; + n->next_ptr = p_element; + n->data = _data; + + if (!p_element->prev_ptr) { + _data->first = n; + } else { + p_element->prev_ptr->next_ptr = n; + } + p_element->prev_ptr = n; + + _data->size_cache++; + + return n; +} + bool LinkedList::remove(ListElement *p_I) { if (_data && p_I) { bool ret = _data->erase(p_I); @@ -245,6 +293,9 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); ClassDB::bind_method(D_METHOD("pop_front"), &LinkedList::pop_front); + ClassDB::bind_method(D_METHOD("insert_after", "element", "value"), &LinkedList::insert_after); + ClassDB::bind_method(D_METHOD("insert_before", "element", "value"), &LinkedList::insert_before); + ClassDB::bind_method(D_METHOD("find", "value"), &LinkedList::find); ClassDB::bind_method(D_METHOD("erase", "value"), &LinkedList::erase); ClassDB::bind_method(D_METHOD("remove", "element"), &LinkedList::remove); diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 3ff262e6..3bf0f4fa 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -75,6 +75,32 @@ func test_push_pop_front(): assert_null(list.front) +func test_insert_before(): + var elements = populate_test_data(list) + list.insert_before(elements[2], "Godot") + assert_eq(list.front.next.next.value, "Godot") + assert_eq(list.back.prev.prev.value, "Godot") + + +func test_insert_before_front(): + var elements = populate_test_data(list) + list.insert_before(list.front, "Godot") + assert_eq(list.front.value, "Godot") + + +func test_insert_after(): + var elements = populate_test_data(list) + list.insert_after(elements[2], "Godot") + assert_eq(list.front.next.next.next.value, "Godot") + assert_eq(list.back.prev.value, "Godot") + + +func test_insert_after_back(): + var elements = populate_test_data(list) + list.insert_after(list.back, "Godot") + assert_eq(list.back.value, "Godot") + + func test_size(): var elements = populate_test_data(list) var original_size = elements.size() From 02571aaf9bdc760a39bd05c01c3ac9ad77b82cca Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 20:08:58 +0300 Subject: [PATCH 14/25] Fix missing argument names for linked list methods --- core/types/list.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 1e250259..a3089355 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -304,12 +304,12 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &LinkedList::clear); ClassDB::bind_method(D_METHOD("size"), &LinkedList::size); - ClassDB::bind_method(D_METHOD("swap"), &LinkedList::swap); + ClassDB::bind_method(D_METHOD("swap", "element_A", "element_B"), &LinkedList::swap); ClassDB::bind_method(D_METHOD("invert"), &LinkedList::invert); - ClassDB::bind_method(D_METHOD("move_to_front"), &LinkedList::move_to_front); - ClassDB::bind_method(D_METHOD("move_to_back"), &LinkedList::move_to_back); - ClassDB::bind_method(D_METHOD("move_before"), &LinkedList::move_before); + ClassDB::bind_method(D_METHOD("move_to_front", "element"), &LinkedList::move_to_front); + ClassDB::bind_method(D_METHOD("move_to_back", "element"), &LinkedList::move_to_back); + ClassDB::bind_method(D_METHOD("move_before", "element", "before_element"), &LinkedList::move_before); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); From bb1f94e6ff76fcdc1edc11a9e217d6c6e2e849f8 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 13 Sep 2020 21:45:48 +0300 Subject: [PATCH 15/25] Implement custom iterators for `LinkedList` Removes the need for allocating array as in `Node.get_children()`. ```gdscript var list := LinkedList.new() list.push_back("Goost") list.push_back(37) list.push_back(Color.blue) for element in list: print(element) ``` --- core/types/list.cpp | 18 ++++++++++++++++++ core/types/list.h | 7 +++++++ tests/project/goost/core/types/test_list.gd | 6 ++++++ 3 files changed, 31 insertions(+) diff --git a/core/types/list.cpp b/core/types/list.cpp index a3089355..c5dd921b 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -313,6 +313,24 @@ void LinkedList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); + + ClassDB::bind_method(D_METHOD("_iter_init"), &LinkedList::_iter_init); + ClassDB::bind_method(D_METHOD("_iter_get"), &LinkedList::_iter_get); + ClassDB::bind_method(D_METHOD("_iter_next"), &LinkedList::_iter_next); +} + +Variant LinkedList::_iter_init(const Array &p_iter) { + _iter_current = get_front(); + return _iter_current != nullptr; +} + +Variant LinkedList::_iter_next(const Array &p_iter) { + _iter_current = _iter_current->get_next(); + return _iter_current != nullptr; +} + +Variant LinkedList::_iter_get(const Variant &p_iter) { + return _iter_current->get_value(); } void LinkedList::clear() { diff --git a/core/types/list.h b/core/types/list.h index 438d92c7..917f474c 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -53,6 +53,13 @@ class LinkedList : public Reference { private: ListData *_data = nullptr; +protected: + // Custom iterator for scripting. + ListElement *_iter_current = nullptr; + Variant _iter_init(const Array &p_iter); + Variant _iter_next(const Array &p_iter); + Variant _iter_get(const Variant &p_iter); + public: ListElement *get_front() { return _data ? _data->first : 0; } ListElement *get_back() { return _data ? _data->last : 0; } diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 3bf0f4fa..ef1d40d2 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -402,6 +402,12 @@ func test_move_before(): assert_eq(list.front.next.value, elements[3].value) +func test_custom_iterators(): + populate_test_data(list) + for element in list: + gut.p(element) + + func test_cleanup(): assert_eq(list.size(), 0) var n = list.push_back("Goost") From 128c4c309e6da95b8630d7c777997ad7b9c6efe2 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Mon, 14 Sep 2020 13:16:02 +0300 Subject: [PATCH 16/25] Add `LinkedList.get_elements()` Turns out iterating over a list with a custom for loop iterator is somewhat slower via script than doing the same thing by allocating an array, but both options are acceptible. --- core/types/list.cpp | 12 ++++++++++++ core/types/list.h | 2 ++ tests/project/goost/core/types/test_list.gd | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/core/types/list.cpp b/core/types/list.cpp index c5dd921b..01485c24 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -87,6 +87,16 @@ void LinkedList::pop_front() { } } +Array LinkedList::get_elements() { + Array elements; + ListElement *it = get_front(); + while (it) { + elements.push_back(it); + it = it->get_next(); + } + return elements; +} + ListElement *LinkedList::insert_after(ListElement *p_element, const Variant &p_value) { CRASH_COND(p_element && (!_data || p_element->data != _data)); @@ -293,6 +303,8 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); ClassDB::bind_method(D_METHOD("pop_front"), &LinkedList::pop_front); + ClassDB::bind_method(D_METHOD("get_elements"), &LinkedList::get_elements); + ClassDB::bind_method(D_METHOD("insert_after", "element", "value"), &LinkedList::insert_after); ClassDB::bind_method(D_METHOD("insert_before", "element", "value"), &LinkedList::insert_before); diff --git a/core/types/list.h b/core/types/list.h index 917f474c..ffd02168 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -69,6 +69,8 @@ class LinkedList : public Reference { ListElement *push_front(const Variant &value); void pop_front(); + Array get_elements(); + ListElement *insert_after(ListElement *p_element, const Variant &p_value); ListElement *insert_before(ListElement *p_element, const Variant &p_value); diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index ef1d40d2..10eabfcf 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -75,6 +75,17 @@ func test_push_pop_front(): assert_null(list.front) +func test_get_elements(): + var test_elements = populate_test_data(list) + for element in list.get_elements(): + gut.p(element) + var elements = list.get_elements() + assert_eq(elements[0], test_elements[0]) + assert_eq(elements[1], test_elements[1]) + assert_eq(elements[2], test_elements[2]) + assert_eq(elements[3], test_elements[3]) + + func test_insert_before(): var elements = populate_test_data(list) list.insert_before(elements[2], "Godot") From 5a1c7a5f963d18f73cf55cbff909b169ecdae1e1 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Mon, 14 Sep 2020 13:22:30 +0300 Subject: [PATCH 17/25] Fix warnings in linked list test cases --- tests/project/goost/core/types/test_list.gd | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 10eabfcf..6dc7396f 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -88,27 +88,31 @@ func test_get_elements(): func test_insert_before(): var elements = populate_test_data(list) - list.insert_before(elements[2], "Godot") + var n = list.insert_before(elements[2], "Godot") + assert_eq(n.value, "Godot") assert_eq(list.front.next.next.value, "Godot") assert_eq(list.back.prev.prev.value, "Godot") func test_insert_before_front(): - var elements = populate_test_data(list) - list.insert_before(list.front, "Godot") + var _elements = populate_test_data(list) + var n = list.insert_before(list.front, "Godot") + assert_eq(n.value, "Godot") assert_eq(list.front.value, "Godot") func test_insert_after(): var elements = populate_test_data(list) - list.insert_after(elements[2], "Godot") + var n = list.insert_after(elements[2], "Godot") + assert_eq(n.value, "Godot") assert_eq(list.front.next.next.next.value, "Godot") assert_eq(list.back.prev.value, "Godot") func test_insert_after_back(): - var elements = populate_test_data(list) - list.insert_after(list.back, "Godot") + var _elements = populate_test_data(list) + var n = list.insert_after(list.back, "Godot") + assert_eq(n.value, "Godot") assert_eq(list.back.value, "Godot") From a905a67ebd5e067a5ca52292f3a0056465134d1b Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Mon, 14 Sep 2020 21:37:07 +0300 Subject: [PATCH 18/25] Rename `ListElement` to `ListNode` The name is quite common as seen in various linked list tutorials over the Internet. This may not particularly true if you go into details of what's considered an item in a particular collection, but this is just too picky to think about this, the engine caters to beginner use cases so I don't see a reason why this should be made an abstract concept, especially since it's likely that no other similar data structure would be ever implemented in Goost (but only time will tell!) --- core/register_core_types.cpp | 2 +- core/types/list.cpp | 115 ++++++++++---------- core/types/list.h | 75 ++++++------- tests/project/goost/core/types/test_list.gd | 114 ++++++++++--------- 4 files changed, 162 insertions(+), 144 deletions(-) diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index e9ae9d7e..399bff2f 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -7,8 +7,8 @@ namespace goost { void register_core_types() { + ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); #ifdef GOOST_IMAGE_ENABLED register_image_types(); #endif diff --git a/core/types/list.cpp b/core/types/list.cpp index 01485c24..2f671b7f 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -1,6 +1,6 @@ #include "list.h" -bool ListData::erase(ListElement *p_I) { +bool ListData::erase(ListNode *p_I) { ERR_FAIL_COND_V(!p_I, false); ERR_FAIL_COND_V(p_I->data != this, false); @@ -22,14 +22,14 @@ bool ListData::erase(ListElement *p_I) { return true; } -ListElement *LinkedList::push_back(const Variant &value) { +ListNode *LinkedList::push_back(const Variant &value) { if (!_data) { _data = memnew(ListData); _data->first = nullptr; _data->last = nullptr; _data->size_cache = 0; } - ListElement *n = memnew(ListElement); + ListNode *n = memnew(ListNode); n->value = value; n->prev_ptr = _data->last; @@ -55,14 +55,14 @@ void LinkedList::pop_back() { } } -ListElement *LinkedList::push_front(const Variant &value) { +ListNode *LinkedList::push_front(const Variant &value) { if (!_data) { _data = memnew(ListData); _data->first = nullptr; _data->last = nullptr; _data->size_cache = 0; } - ListElement *n = memnew(ListElement); + ListNode *n = memnew(ListNode); n->value = value; n->prev_ptr = 0; n->next_ptr = _data->first; @@ -87,65 +87,69 @@ void LinkedList::pop_front() { } } -Array LinkedList::get_elements() { - Array elements; - ListElement *it = get_front(); +Array LinkedList::get_nodes() { + Array nodes; + ListNode *it = get_front(); while (it) { - elements.push_back(it); + nodes.push_back(it); it = it->get_next(); } - return elements; + return nodes; +} + +Array LinkedList::get_elements() { + return get_nodes(); } -ListElement *LinkedList::insert_after(ListElement *p_element, const Variant &p_value) { - CRASH_COND(p_element && (!_data || p_element->data != _data)); +ListNode *LinkedList::insert_after(ListNode *p_node, const Variant &p_value) { + CRASH_COND(p_node && (!_data || p_node->data != _data)); - if (!p_element) { + if (!p_node) { return push_back(p_value); } - ListElement *n = memnew(ListElement); + ListNode *n = memnew(ListNode); n->value = p_value; - n->prev_ptr = p_element; - n->next_ptr = p_element->next_ptr; + n->prev_ptr = p_node; + n->next_ptr = p_node->next_ptr; n->data = _data; - if (!p_element->next_ptr) { + if (!p_node->next_ptr) { _data->last = n; } else { - p_element->next_ptr->prev_ptr = n; + p_node->next_ptr->prev_ptr = n; } - p_element->next_ptr = n; + p_node->next_ptr = n; _data->size_cache++; return n; } -ListElement *LinkedList::insert_before(ListElement *p_element, const Variant &p_value) { - CRASH_COND(p_element && (!_data || p_element->data != _data)); +ListNode *LinkedList::insert_before(ListNode *p_node, const Variant &p_value) { + CRASH_COND(p_node && (!_data || p_node->data != _data)); - if (!p_element) { + if (!p_node) { return push_back(p_value); } - ListElement *n = memnew(ListElement); + ListNode *n = memnew(ListNode); n->value = p_value; - n->prev_ptr = p_element->prev_ptr; - n->next_ptr = p_element; + n->prev_ptr = p_node->prev_ptr; + n->next_ptr = p_node; n->data = _data; - if (!p_element->prev_ptr) { + if (!p_node->prev_ptr) { _data->first = n; } else { - p_element->prev_ptr->next_ptr = n; + p_node->prev_ptr->next_ptr = n; } - p_element->prev_ptr = n; + p_node->prev_ptr = n; _data->size_cache++; return n; } -bool LinkedList::remove(ListElement *p_I) { +bool LinkedList::remove(ListNode *p_I) { if (_data && p_I) { bool ret = _data->erase(p_I); if (_data->size_cache == 0) { @@ -157,8 +161,8 @@ bool LinkedList::remove(ListElement *p_I) { return false; }; -ListElement *LinkedList::find(const Variant &p_value) { - ListElement *it = get_front(); +ListNode *LinkedList::find(const Variant &p_value) { + ListNode *it = get_front(); while (it) { if (it->value == p_value) { return it; @@ -169,11 +173,11 @@ ListElement *LinkedList::find(const Variant &p_value) { } bool LinkedList::erase(const Variant &p_value) { - ListElement *I = find(p_value); + ListNode *I = find(p_value); return remove(I); } -void LinkedList::swap(ListElement *p_A, ListElement *p_B) { +void LinkedList::swap(ListNode *p_A, ListNode *p_B) { ERR_FAIL_COND(!p_A || !p_B); ERR_FAIL_COND(p_A->data != _data); ERR_FAIL_COND(p_B->data != _data); @@ -181,10 +185,10 @@ void LinkedList::swap(ListElement *p_A, ListElement *p_B) { if (p_A == p_B) { return; } - ListElement *A_prev = p_A->prev_ptr; - ListElement *A_next = p_A->next_ptr; - ListElement *B_prev = p_B->prev_ptr; - ListElement *B_next = p_B->next_ptr; + ListNode *A_prev = p_A->prev_ptr; + ListNode *A_next = p_A->next_ptr; + ListNode *B_prev = p_B->prev_ptr; + ListNode *B_next = p_B->next_ptr; if (A_prev) { A_prev->next_ptr = p_B; @@ -214,8 +218,8 @@ void LinkedList::swap(ListElement *p_A, ListElement *p_B) { void LinkedList::invert() { int s = size() / 2; - ListElement *F = get_front(); - ListElement *B = get_back(); + ListNode *F = get_front(); + ListNode *B = get_back(); for (int i = 0; i < s; i++) { SWAP(F->value, B->value); F = F->get_next(); @@ -223,7 +227,7 @@ void LinkedList::invert() { } } -void LinkedList::move_to_back(ListElement *p_I) { +void LinkedList::move_to_back(ListNode *p_I) { ERR_FAIL_COND(p_I->data != _data); if (!p_I->next_ptr) { return; @@ -245,7 +249,7 @@ void LinkedList::move_to_back(ListElement *p_I) { _data->last = p_I; } -void LinkedList::move_to_front(ListElement *p_I) { +void LinkedList::move_to_front(ListNode *p_I) { ERR_FAIL_COND(p_I->data != _data); if (!p_I->prev_ptr) { return; @@ -267,7 +271,7 @@ void LinkedList::move_to_front(ListElement *p_I) { _data->first = p_I; } -void LinkedList::move_before(ListElement *p_A, ListElement *p_B) { +void LinkedList::move_before(ListNode *p_A, ListNode *p_B) { if (p_A->prev_ptr) { p_A->prev_ptr->next_ptr = p_A->next_ptr; } else { @@ -303,25 +307,26 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("push_front", "value"), &LinkedList::push_front); ClassDB::bind_method(D_METHOD("pop_front"), &LinkedList::pop_front); + ClassDB::bind_method(D_METHOD("get_nodes"), &LinkedList::get_nodes); ClassDB::bind_method(D_METHOD("get_elements"), &LinkedList::get_elements); - ClassDB::bind_method(D_METHOD("insert_after", "element", "value"), &LinkedList::insert_after); - ClassDB::bind_method(D_METHOD("insert_before", "element", "value"), &LinkedList::insert_before); + ClassDB::bind_method(D_METHOD("insert_after", "node", "value"), &LinkedList::insert_after); + ClassDB::bind_method(D_METHOD("insert_before", "node", "value"), &LinkedList::insert_before); ClassDB::bind_method(D_METHOD("find", "value"), &LinkedList::find); ClassDB::bind_method(D_METHOD("erase", "value"), &LinkedList::erase); - ClassDB::bind_method(D_METHOD("remove", "element"), &LinkedList::remove); + ClassDB::bind_method(D_METHOD("remove", "node"), &LinkedList::remove); ClassDB::bind_method(D_METHOD("empty"), &LinkedList::empty); ClassDB::bind_method(D_METHOD("clear"), &LinkedList::clear); ClassDB::bind_method(D_METHOD("size"), &LinkedList::size); - ClassDB::bind_method(D_METHOD("swap", "element_A", "element_B"), &LinkedList::swap); + ClassDB::bind_method(D_METHOD("swap", "node_A", "node_B"), &LinkedList::swap); ClassDB::bind_method(D_METHOD("invert"), &LinkedList::invert); - ClassDB::bind_method(D_METHOD("move_to_front", "element"), &LinkedList::move_to_front); - ClassDB::bind_method(D_METHOD("move_to_back", "element"), &LinkedList::move_to_back); - ClassDB::bind_method(D_METHOD("move_before", "element", "before_element"), &LinkedList::move_before); + ClassDB::bind_method(D_METHOD("move_to_front", "node"), &LinkedList::move_to_front); + ClassDB::bind_method(D_METHOD("move_to_back", "node"), &LinkedList::move_to_back); + ClassDB::bind_method(D_METHOD("move_before", "node", "before_node"), &LinkedList::move_before); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); @@ -351,14 +356,14 @@ void LinkedList::clear() { } } -void ListElement::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_next"), &ListElement::get_next); - ClassDB::bind_method(D_METHOD("get_prev"), &ListElement::get_prev); +void ListNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_next"), &ListNode::get_next); + ClassDB::bind_method(D_METHOD("get_prev"), &ListNode::get_prev); - ClassDB::bind_method(D_METHOD("set_value", "value"), &ListElement::set_value); - ClassDB::bind_method(D_METHOD("get_value"), &ListElement::get_value); + ClassDB::bind_method(D_METHOD("set_value", "value"), &ListNode::set_value); + ClassDB::bind_method(D_METHOD("get_value"), &ListNode::get_value); - ClassDB::bind_method(D_METHOD("erase"), &ListElement::erase); + ClassDB::bind_method(D_METHOD("erase"), &ListNode::erase); ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "next"), "", "get_next"); diff --git a/core/types/list.h b/core/types/list.h index ffd02168..a24069fd 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -8,17 +8,17 @@ #include "core/reference.h" #include "core/sort_array.h" -class ListElement; +class ListNode; struct ListData { - ListElement *first = nullptr; - ListElement *last = nullptr; + ListNode *first = nullptr; + ListNode *last = nullptr; int size_cache = 0; - bool erase(ListElement *p_I); + bool erase(ListNode *p_node); }; -class ListElement : public Object { - GDCLASS(ListElement, Object); +class ListNode : public Object { + GDCLASS(ListNode, Object); protected: static void _bind_methods(); @@ -28,20 +28,20 @@ class ListElement : public Object { friend struct ListData; Variant value; - ListElement *next_ptr = nullptr; - ListElement *prev_ptr = nullptr; + ListNode *next_ptr = nullptr; + ListNode *prev_ptr = nullptr; ListData *data = nullptr; public: - ListElement *get_next() { return next_ptr; } - ListElement *get_prev() { return prev_ptr; } + ListNode *get_next() { return next_ptr; } + ListNode *get_prev() { return prev_ptr; } Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } void erase() { data->erase(this); } - ListElement() {} + ListNode() {} }; class LinkedList : public Reference { @@ -55,39 +55,40 @@ class LinkedList : public Reference { protected: // Custom iterator for scripting. - ListElement *_iter_current = nullptr; + ListNode *_iter_current = nullptr; Variant _iter_init(const Array &p_iter); Variant _iter_next(const Array &p_iter); Variant _iter_get(const Variant &p_iter); public: - ListElement *get_front() { return _data ? _data->first : 0; } - ListElement *get_back() { return _data ? _data->last : 0; } + ListNode *get_front() { return _data ? _data->first : 0; } + ListNode *get_back() { return _data ? _data->last : 0; } - ListElement *push_back(const Variant &value); + ListNode *push_back(const Variant &value); void pop_back(); - ListElement *push_front(const Variant &value); + ListNode *push_front(const Variant &value); void pop_front(); - Array get_elements(); + Array get_nodes(); + Array get_elements(); // Alias for `get_nodes`. - ListElement *insert_after(ListElement *p_element, const Variant &p_value); - ListElement *insert_before(ListElement *p_element, const Variant &p_value); + ListNode *insert_after(ListNode *p_node, const Variant &p_value); + ListNode *insert_before(ListNode *p_node, const Variant &p_value); - ListElement *find(const Variant &p_value); + ListNode *find(const Variant &p_value); - bool remove(ListElement *p_I); + bool remove(ListNode *p_node); bool erase(const Variant &value); bool empty() const { return !_data || !_data->size_cache; } void clear(); int size() const { return _data ? _data->size_cache : 0; } - void swap(ListElement *p_A, ListElement *p_B); + void swap(ListNode *p_node_A, ListNode *p_node_B); void invert(); - void move_to_back(ListElement *p_I); - void move_to_front(ListElement *p_I); - void move_before(ListElement *p_A, ListElement *p_B); + void move_to_back(ListNode *p_node); + void move_to_front(ListNode *p_node); + void move_before(ListNode *p_node_A, ListNode *p_node_B); void sort() { sort_custom>(); @@ -98,18 +99,18 @@ class LinkedList : public Reference { if (size() < 2) return; - ListElement *from = get_front(); - ListElement *current = from; - ListElement *to = from; + ListNode *from = get_front(); + ListNode *current = from; + ListNode *to = from; while (current) { - ListElement *next = current->next_ptr; + ListNode *next = current->next_ptr; if (from != current) { current->prev_ptr = nullptr; current->next_ptr = from; - ListElement *find = from; + ListNode *find = from; C less; while (find && less(find->value, current->value)) { current->prev_ptr = find; @@ -140,7 +141,7 @@ class LinkedList : public Reference { template struct AuxiliaryComparator { C compare; - bool operator()(const ListElement *a, const ListElement *b) const { + bool operator()(const ListNode *a, const ListNode *b) const { return compare(a->value, b->value); } }; @@ -151,15 +152,15 @@ class LinkedList : public Reference { if (s < 2) return; - ListElement **aux_buffer = memnew_arr(ListElement *, s); + ListNode **aux_buffer = memnew_arr(ListNode *, s); int idx = 0; - for (ListElement *E = get_front(); E; E = E->next_ptr) { + for (ListNode *E = get_front(); E; E = E->next_ptr) { aux_buffer[idx] = E; idx++; } - SortArray> sort; + SortArray> sort; sort.sort(aux_buffer, s); _data->first = aux_buffer[0]; @@ -182,9 +183,9 @@ class LinkedList : public Reference { }; template <> -struct VariantCaster { - static _FORCE_INLINE_ ListElement *cast(const Variant &p_variant) { - return (ListElement *)p_variant.operator Object *(); +struct VariantCaster { + static _FORCE_INLINE_ ListNode *cast(const Variant &p_variant) { + return (ListNode *)p_variant.operator Object *(); } }; diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 6dc7396f..89a9fe0f 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -8,24 +8,24 @@ func before_each(): func populate_test_data(p_list: LinkedList): - var elements = [] - elements.append(p_list.push_back("Goost")) - elements.append(p_list.push_back(37)) - elements.append(p_list.push_back(Vector2.ONE)) - elements.append(p_list.push_back(Color.blue)) - return elements + var nodes = [] + nodes.append(p_list.push_back("Goost")) + nodes.append(p_list.push_back(37)) + nodes.append(p_list.push_back(Vector2.ONE)) + nodes.append(p_list.push_back(Color.blue)) + return nodes -func populate_test_data_integers(p_list: LinkedList, p_num_elements: int): - var elements = [] - for i in p_num_elements: - elements.append(p_list.push_back(i)) - return elements +func populate_test_data_integers(p_list: LinkedList, p_num_nodes: int): + var nodes = [] + for i in p_num_nodes: + nodes.append(p_list.push_back(i)) + return nodes func test_push_pop_back(): # Push back - var n: ListElement + var n: ListNode n = list.push_back("Goost") assert_eq(n.value, "Goost") n = list.push_back(37) @@ -51,7 +51,7 @@ func test_push_pop_back(): func test_push_pop_front(): # Push front - var n: ListElement + var n: ListNode n = list.push_front("Goost") assert_eq(n.value, "Goost") n = list.push_front(37) @@ -75,50 +75,62 @@ func test_push_pop_front(): assert_null(list.front) +func test_get_nodes(): + var test_nodes = populate_test_data(list) + for node in list.get_nodes(): + gut.p(node) + var nodes = list.get_nodes() + assert_eq(nodes[0], test_nodes[0]) + assert_eq(nodes[1], test_nodes[1]) + assert_eq(nodes[2], test_nodes[2]) + assert_eq(nodes[3], test_nodes[3]) + + func test_get_elements(): - var test_elements = populate_test_data(list) - for element in list.get_elements(): - gut.p(element) - var elements = list.get_elements() - assert_eq(elements[0], test_elements[0]) - assert_eq(elements[1], test_elements[1]) - assert_eq(elements[2], test_elements[2]) - assert_eq(elements[3], test_elements[3]) + # Alias for `get_nodes`. + var test_nodes = populate_test_data(list) + for node in list.get_elements(): + gut.p(node) + var nodes = list.get_elements() + assert_eq(nodes[0], test_nodes[0]) + assert_eq(nodes[1], test_nodes[1]) + assert_eq(nodes[2], test_nodes[2]) + assert_eq(nodes[3], test_nodes[3]) func test_insert_before(): - var elements = populate_test_data(list) - var n = list.insert_before(elements[2], "Godot") + var nodes = populate_test_data(list) + var n = list.insert_before(nodes[2], "Godot") assert_eq(n.value, "Godot") assert_eq(list.front.next.next.value, "Godot") assert_eq(list.back.prev.prev.value, "Godot") func test_insert_before_front(): - var _elements = populate_test_data(list) + var _nodes = populate_test_data(list) var n = list.insert_before(list.front, "Godot") assert_eq(n.value, "Godot") assert_eq(list.front.value, "Godot") func test_insert_after(): - var elements = populate_test_data(list) - var n = list.insert_after(elements[2], "Godot") + var nodes = populate_test_data(list) + var n = list.insert_after(nodes[2], "Godot") assert_eq(n.value, "Godot") assert_eq(list.front.next.next.next.value, "Godot") assert_eq(list.back.prev.value, "Godot") func test_insert_after_back(): - var _elements = populate_test_data(list) + var _nodes = populate_test_data(list) var n = list.insert_after(list.back, "Godot") assert_eq(n.value, "Godot") assert_eq(list.back.value, "Godot") func test_size(): - var elements = populate_test_data(list) - var original_size = elements.size() + var nodes = populate_test_data(list) + var original_size = nodes.size() assert_eq(list.size(), original_size) @@ -138,8 +150,8 @@ func test_find(): func test_erase(): - var elements = populate_test_data(list) - var original_size = elements.size() + var nodes = populate_test_data(list) + var original_size = nodes.size() var erased = list.erase(Color.blue) assert_true(erased) assert_eq(list.size(), original_size - 1) @@ -148,8 +160,8 @@ func test_erase(): func test_remove(): - var elements = populate_test_data(list) - var original_size = elements.size() + var nodes = populate_test_data(list) + var original_size = nodes.size() var removed = list.remove(list.find("Goost")) assert_true(removed) assert_eq(list.size(), original_size - 1) @@ -158,7 +170,7 @@ func test_remove(): func test_empty(): populate_test_data(list) - var n: ListElement = list.front + var n: ListNode = list.front while n: var removed = list.remove(n) assert_true(removed) @@ -327,12 +339,12 @@ func test_swap_random(): var vb = b.value list.swap(a, b) - var num_elements = 0 + var num_nodes = 0 # Fully traversable after swap? var it = list.front while it: - num_elements += 1 + num_nodes += 1 var prev_it = it it = it.next if it == prev_it: @@ -341,15 +353,15 @@ func test_swap_random(): # Even if traversed, # we should not lose anything in the process. - if num_elements != size: - assert_true(false, "Element count mismatch.") + if num_nodes != size: + assert_true(false, "Node count mismatch.") break assert_eq(va, a.value) assert_eq(vb, b.value) -func test_swap_front_and_back_2_elements(): +func test_swap_front_and_back_2_nodes(): var a = list.push_back("Goost") var b = list.push_back(Color.blue) @@ -359,7 +371,7 @@ func test_swap_front_and_back_2_elements(): assert_eq(list.back.value, "Goost") -func test_swap_front_and_back_3_elements(): +func test_swap_front_and_back_3_nodes(): var a = list.push_back("Goost") var _b = list.push_back(37) var c = list.push_back(Vector2.ONE) @@ -370,7 +382,7 @@ func test_swap_front_and_back_3_elements(): assert_eq(list.back.value, "Goost") -func test_swap_front_and_back_4_elements(): +func test_swap_front_and_back_4_nodes(): var a = list.push_back("Goost") var _b = list.push_back(37) var _c = list.push_back(Vector2.ONE) @@ -399,28 +411,28 @@ func test_invert(): func test_move_to_front(): - var elements = populate_test_data(list) - list.move_to_front(elements[2]) + var nodes = populate_test_data(list) + list.move_to_front(nodes[2]) assert_eq(list.front.value, Vector2.ONE) func test_move_to_back(): - var elements = populate_test_data(list) - list.move_to_back(elements[0]) + var nodes = populate_test_data(list) + list.move_to_back(nodes[0]) assert_eq(list.back.value, "Goost") func test_move_before(): - var elements = populate_test_data(list) - assert_eq(elements[3].value, Color.blue) - list.move_before(elements[3], elements[1]) - assert_eq(list.front.next.value, elements[3].value) + var nodes = populate_test_data(list) + assert_eq(nodes[3].value, Color.blue) + list.move_before(nodes[3], nodes[1]) + assert_eq(list.front.next.value, nodes[3].value) func test_custom_iterators(): populate_test_data(list) - for element in list: - gut.p(element) + for node in list: + gut.p(node) func test_cleanup(): From ad118a409698d748d49db64039b1979ec734f194 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Mon, 14 Sep 2020 22:22:29 +0300 Subject: [PATCH 19/25] Expose linked list `sort` method --- core/types/list.cpp | 2 ++ core/types/list.h | 23 ++++++++---------- tests/project/goost/core/types/test_list.gd | 26 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 2f671b7f..1075c8b5 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -328,6 +328,8 @@ void LinkedList::_bind_methods() { ClassDB::bind_method(D_METHOD("move_to_back", "node"), &LinkedList::move_to_back); ClassDB::bind_method(D_METHOD("move_before", "node", "before_node"), &LinkedList::move_before); + ClassDB::bind_method(D_METHOD("sort"), &LinkedList::sort); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); diff --git a/core/types/list.h b/core/types/list.h index a24069fd..19250bec 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -96,9 +96,9 @@ class LinkedList : public Reference { template void sort_custom_inplace() { - if (size() < 2) + if (size() < 2) { return; - + } ListNode *from = get_front(); ListNode *current = from; ListNode *to = from; @@ -117,21 +117,20 @@ class LinkedList : public Reference { current->next_ptr = find->next_ptr; find = find->next_ptr; } - - if (current->prev_ptr) + if (current->prev_ptr) { current->prev_ptr->next_ptr = current; - else + } else { from = current; - - if (current->next_ptr) + } + if (current->next_ptr) { current->next_ptr->prev_ptr = current; - else + } else { to = current; + } } else { current->prev_ptr = nullptr; current->next_ptr = nullptr; } - current = next; } _data->first = from; @@ -149,9 +148,9 @@ class LinkedList : public Reference { template void sort_custom() { int s = size(); - if (s < 2) + if (s < 2) { return; - + } ListNode **aux_buffer = memnew_arr(ListNode *, s); int idx = 0; @@ -159,7 +158,6 @@ class LinkedList : public Reference { aux_buffer[idx] = E; idx++; } - SortArray> sort; sort.sort(aux_buffer, s); @@ -175,7 +173,6 @@ class LinkedList : public Reference { aux_buffer[i]->prev_ptr = aux_buffer[i - 1]; aux_buffer[i]->next_ptr = aux_buffer[i + 1]; } - memdelete_arr(aux_buffer); } LinkedList() {} diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 89a9fe0f..ee2ed0f0 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -435,6 +435,32 @@ func test_custom_iterators(): gut.p(node) +func tets_sort(): + var n: ListNode + n = list.push_back("B") + assert_eq(n.value, "B") + n = list.push_back("D") + assert_eq(n.value, "D") + n = list.push_back("A") + assert_eq(n.value, "A") + n = list.push_back("C") + assert_eq(n.value, "C") + + list.sort() + + assert_eq(list.front.value, "A") + assert_eq(list.front.next.value, "B") + assert_eq(list.back.prev.value, "C") + assert_eq(list.back.value, "D") + + var abcd = ["A", "B", "C", "D"] + var abcd_list = list.get_nodes() + + for i in 4: + gut.p(abcd_list[i]) + assert_eq(abcd[i], abcd_list[i]) + + func test_cleanup(): assert_eq(list.size(), 0) var n = list.push_back("Goost") From 71e5b2142ebe67ffb40359fb22b486e972b67070 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Tue, 15 Sep 2020 20:22:29 +0300 Subject: [PATCH 20/25] Stringify linked list and list nodes for printing This should be available in Godot >=3.2.3 or so. Not using the `override` keyword in this case, makes it backward compatible. --- core/types/list.cpp | 16 +++++++++++++++- core/types/list.h | 4 ++++ tests/project/goost/core/types/test_list.gd | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 1075c8b5..04ad0757 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -332,7 +332,7 @@ void LinkedList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "front"), "", "get_front"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "back"), "", "get_back"); - + ClassDB::bind_method(D_METHOD("_iter_init"), &LinkedList::_iter_init); ClassDB::bind_method(D_METHOD("_iter_get"), &LinkedList::_iter_get); ClassDB::bind_method(D_METHOD("_iter_next"), &LinkedList::_iter_next); @@ -358,6 +358,20 @@ void LinkedList::clear() { } } +String LinkedList::to_string() { + String str("["); + ListNode *it = get_front(); + while (it) { + if (it != get_front()) { + str += ", "; + } + str += String(it->get_value()); + it = it->get_next(); + } + str += "]"; + return str; +} + void ListNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_next"), &ListNode::get_next); ClassDB::bind_method(D_METHOD("get_prev"), &ListNode::get_prev); diff --git a/core/types/list.h b/core/types/list.h index 19250bec..463bf998 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -41,6 +41,8 @@ class ListNode : public Object { void erase() { data->erase(this); } + virtual String to_string() { return String(value); } + ListNode() {} }; @@ -94,6 +96,8 @@ class LinkedList : public Reference { sort_custom>(); } + virtual String to_string(); + template void sort_custom_inplace() { if (size() < 2) { diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index ee2ed0f0..a4719ad2 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -461,6 +461,16 @@ func tets_sort(): assert_eq(abcd[i], abcd_list[i]) +func test_print_list_node(): + var node = list.push_back("Goost") + gut.p(node) + + +func test_print_list(): + populate_test_data(list) + gut.p(list) + + func test_cleanup(): assert_eq(list.size(), 0) var n = list.push_back("Goost") From d8c1795d5e7c8e9ba2aea692fd23c2964c19a72b Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Tue, 15 Sep 2020 21:29:40 +0300 Subject: [PATCH 21/25] Add `LinkedList::create_from` method Supports creating list nodes from any `Variant` compatible datatype. --- core/types/list.cpp | 36 ++++++++++++ core/types/list.h | 2 + tests/project/goost/core/types/test_list.gd | 63 +++++++++++++++++++-- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index 04ad0757..c29e829c 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -22,6 +22,40 @@ bool ListData::erase(ListNode *p_I) { return true; } +void LinkedList::create_from(const Variant &p_value) { + clear(); + switch (p_value.get_type()) { + case Variant::NIL: { + // Do nothing. + } break; + case Variant::DICTIONARY: { + List keys; + Dictionary dict = p_value; + dict.get_key_list(&keys); + for (List::Element *E = keys.front(); E; E = E->next()) { + ListNode *n = push_back(E->get()); // Key. + n->set_meta("value", dict[E->get()]); // Value. + } + } break; + case Variant::ARRAY: + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: + case Variant::POOL_COLOR_ARRAY: { + Array arr = p_value; + for (int i = 0; i < arr.size(); ++i) { + push_back(arr[i]); + } + } break; + default: { + push_back(p_value); + } + } +} + ListNode *LinkedList::push_back(const Variant &value) { if (!_data) { _data = memnew(ListData); @@ -299,6 +333,8 @@ void LinkedList::move_before(ListNode *p_A, ListNode *p_B) { } void LinkedList::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from", "value"), &LinkedList::create_from); + ClassDB::bind_method(D_METHOD("get_front"), &LinkedList::get_front); ClassDB::bind_method(D_METHOD("get_back"), &LinkedList::get_back); diff --git a/core/types/list.h b/core/types/list.h index 463bf998..cf74e6ea 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -63,6 +63,8 @@ class LinkedList : public Reference { Variant _iter_get(const Variant &p_iter); public: + void create_from(const Variant &p_value); + ListNode *get_front() { return _data ? _data->first : 0; } ListNode *get_back() { return _data ? _data->last : 0; } diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index a4719ad2..505582e3 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -6,13 +6,21 @@ var list: LinkedList func before_each(): list = LinkedList.new() + +func get_test_data(): + var data = [ + "Goost", + 37, + Vector2.ONE, + Color.blue, + ] + return data + func populate_test_data(p_list: LinkedList): var nodes = [] - nodes.append(p_list.push_back("Goost")) - nodes.append(p_list.push_back(37)) - nodes.append(p_list.push_back(Vector2.ONE)) - nodes.append(p_list.push_back(Color.blue)) + for v in get_test_data(): + nodes.append(p_list.push_back(v)) return nodes @@ -471,6 +479,53 @@ func test_print_list(): gut.p(list) +func test_create_from_null(): + list.create_from(null) + assert_null(list.front) + assert_null(list.back) + assert_true(list.empty()) + assert_eq(list.size(), 0) + + +func test_create_from_array(): + var array = get_test_data() + list.create_from(array) + assert_eq(list.front.value, "Goost") + assert_eq(list.front.next.value, 37) + assert_eq(list.back.prev.value, Vector2.ONE) + assert_eq(list.back.value, Color.blue) + + +func test_create_from_pool_array(): + var pool_array = PoolIntArray([0, 1, 2, 3]) + list.create_from(pool_array) + assert_eq(list.front.value, 0) + assert_eq(list.front.next.value, 1) + assert_eq(list.back.prev.value, 2) + assert_eq(list.back.value, 3) + + +func test_create_from_dictionary(): + var array = get_test_data() + var dictionary = {} + for v in array: + dictionary[v] = v + + list.create_from(dictionary) + + assert_eq(list.front.value, "Goost") + assert_eq(list.front.get_meta("value"), "Goost") + + assert_eq(list.front.next.value, 37) + assert_eq(list.front.next.get_meta("value"), 37) + + assert_eq(list.back.prev.value, Vector2.ONE) + assert_eq(list.back.prev.get_meta("value"), Vector2.ONE) + + assert_eq(list.back.value, Color.blue) + assert_eq(list.back.get_meta("value"), Color.blue) + + func test_cleanup(): assert_eq(list.size(), 0) var n = list.push_back("Goost") From a11b6ec367c50c40d68d50d4ac929f4863679c49 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Tue, 15 Sep 2020 22:37:56 +0300 Subject: [PATCH 22/25] Add editor icons for `LinkedList` and `ListNode` --- editor/icons/icon_linked_list.svg | 1 + editor/icons/icon_list_node.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 editor/icons/icon_linked_list.svg create mode 100644 editor/icons/icon_list_node.svg diff --git a/editor/icons/icon_linked_list.svg b/editor/icons/icon_linked_list.svg new file mode 100644 index 00000000..1c05351a --- /dev/null +++ b/editor/icons/icon_linked_list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_list_node.svg b/editor/icons/icon_list_node.svg new file mode 100644 index 00000000..9a98b797 --- /dev/null +++ b/editor/icons/icon_list_node.svg @@ -0,0 +1 @@ + \ No newline at end of file From 54754d6d7b0853720467360e8e817f1ebb5ae76e Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Wed, 16 Sep 2020 16:32:17 +0300 Subject: [PATCH 23/25] Document `ListNode` Also fixes crash on attempting to erase orphan nodes. --- core/types/list.h | 2 +- doc/LinkedList.xml | 177 ++++++++++++++++++++ doc/ListNode.xml | 34 ++++ goost.py | 2 + tests/project/goost/core/types/test_list.gd | 74 +++++++- 5 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 doc/LinkedList.xml create mode 100644 doc/ListNode.xml diff --git a/core/types/list.h b/core/types/list.h index cf74e6ea..6b6865ba 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -39,7 +39,7 @@ class ListNode : public Object { Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } - void erase() { data->erase(this); } + void erase() { data ? data->erase(this) : memdelete(this); } virtual String to_string() { return String(value); } diff --git a/doc/LinkedList.xml b/doc/LinkedList.xml new file mode 100644 index 00000000..75bc872c --- /dev/null +++ b/doc/LinkedList.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/ListNode.xml b/doc/ListNode.xml new file mode 100644 index 00000000..a73bd362 --- /dev/null +++ b/doc/ListNode.xml @@ -0,0 +1,34 @@ + + + + A single element in the [LinkedList]. + + + The list node is an elemental building block which represent the entire structure of [LinkedList]s. It has [member next] and [member prev] properties which are assigned whenever a new element is inserted into the list, such as using [method LinkedList.push_back]. + [b]Warning:[/b] do not use [method Object.free] to remove this node from the list it belongs to (unless it is instantiated manually with [code]ListNode.new()[/code]), call [method erase] instead which allows the list to update neighbor node references as well. + + + + + + + + + Erases this node from the [LinkedList] it originates from. + + + + + + The next node this node points to in the [LinkedList]. If [code]null[/code], then this node is the last element in the list, which is equivalent to [member LinkedList.back]. + + + The previous node this node points to in the [LinkedList]. If [code]null[/code], then this node is the first element in the list, which is equivalent to [member LinkedList.front]. + + + The data this node holds, usually initialized with the list methods such as [method LinkedList.push_back]. Can be anything, including other [LinkedList]s. + + + + + diff --git a/goost.py b/goost.py index 14115186..751a3973 100644 --- a/goost.py +++ b/goost.py @@ -51,6 +51,8 @@ def get_child_components(parent): "GoostImage", "ImageBlender", "ImageIndexed", + "LinkedList", + "ListNode", "PolyBooleanParameters2D", "PolyDecompParameters2D", "PolyOffsetParameters2D", diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 505582e3..9e03cb4b 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -6,7 +6,7 @@ var list: LinkedList func before_each(): list = LinkedList.new() - + func get_test_data(): var data = [ "Goost", @@ -494,8 +494,8 @@ func test_create_from_array(): assert_eq(list.front.next.value, 37) assert_eq(list.back.prev.value, Vector2.ONE) assert_eq(list.back.value, Color.blue) - - + + func test_create_from_pool_array(): var pool_array = PoolIntArray([0, 1, 2, 3]) list.create_from(pool_array) @@ -526,6 +526,74 @@ func test_create_from_dictionary(): assert_eq(list.back.get_meta("value"), Color.blue) +func test_list_node_erase(): + var nodes = populate_test_data(list) + assert_not_null(nodes[0]) + assert_not_null(list.find("Goost")) + nodes[0].erase() + assert_null(nodes[0]) + assert_null(list.find("Goost")) + + +func test_list_node_erase_orphan(): + var n = ListNode.new() + n.value = "Goost" + n.erase() # Should not crash. + assert_null(n) + + +# Sorry, this doesn't work, use `ListNode.erase()` instead. +# There seems to be no way to override `free()` or prevent +# such calls from being made as in `Reference.free()`, but +# that's also enforced by GDScript itself rather than core. +# +# func test_list_node_free(): +# var nodes = populate_test_data(list) +# assert_not_null(nodes[0]) +# assert_not_null(list.find("Goost")) +# nodes[0].free() +# nodes[0] = null +# assert_null(nodes[0]) +# assert_null(list.find("Goost")) + + +func test_list_inside_list_node__via_manual_value_set(): + var nodes = populate_test_data(list) + assert_not_null(list.find("Goost")) + var new_list = LinkedList.new() + var new_node = new_list.push_back("Godot") + assert_eq(new_node.value, "Godot") + assert_not_null(list.front) + + # This works. + var n = list.front + n.value = new_list + + # FIXME: the following doesn't work, throws an error: + # 'Invalid set index 'front' (on base: 'LinkedList') with value of type 'ListNode'.' + # Despite the fact that `new_list` is NOT a `ListNode`, + # and the referenced `front` property is incorrect (the above works). + # + # list.front.value = new_list + + assert_eq(list.front.value.front.value, "Godot") + + +func test_list_inside_list_node_nested__via_push_back(): + var new_list = LinkedList.new() + var new_node = new_list.push_back("Godot") + assert_eq(new_node.value, "Godot") + list.push_back(new_list) + assert_eq(list.front.value.front.value, "Godot") + + +func test_list_node_set_value(): + var n = ListNode.new() + n.value = "Goost" + assert_eq(n.value, "Goost") + n.free() + + func test_cleanup(): assert_eq(list.size(), 0) var n = list.push_back("Goost") From da4df2ab5d98f0f074cba766d603905d7e8c73be Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Wed, 16 Sep 2020 16:39:09 +0300 Subject: [PATCH 24/25] Fix ternary operator compilation error for erasing list node --- core/types/list.cpp | 8 ++++++++ core/types/list.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/types/list.cpp b/core/types/list.cpp index c29e829c..5d29c4f4 100644 --- a/core/types/list.cpp +++ b/core/types/list.cpp @@ -408,6 +408,14 @@ String LinkedList::to_string() { return str; } +void ListNode::erase() { + if (data) { + data->erase(this); + } else { + memdelete(this); + } +} + void ListNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_next"), &ListNode::get_next); ClassDB::bind_method(D_METHOD("get_prev"), &ListNode::get_prev); diff --git a/core/types/list.h b/core/types/list.h index 6b6865ba..74369c9d 100644 --- a/core/types/list.h +++ b/core/types/list.h @@ -39,7 +39,7 @@ class ListNode : public Object { Variant get_value() { return value; } void set_value(const Variant &p_value) { value = p_value; } - void erase() { data ? data->erase(this) : memdelete(this); } + void erase(); virtual String to_string() { return String(value); } From 2c8b4e44b21ac73aefd5b074718621daca852757 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Wed, 16 Sep 2020 20:19:56 +0300 Subject: [PATCH 25/25] Document `LinkedList` --- doc/LinkedList.xml | 63 +++++++++++++++++++++ tests/project/goost/core/types/test_list.gd | 39 ++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/doc/LinkedList.xml b/doc/LinkedList.xml index 75bc872c..40bbb5f0 100644 --- a/doc/LinkedList.xml +++ b/doc/LinkedList.xml @@ -1,8 +1,36 @@ + A doubly linked list data structure. + A data structure which consists of a set of sequentially linked elements called nodes. Uses [ListNode] as a basic building block. Each node contains a reference to the previous node, the next node, and the data associated with the node. Insertion and deletion operations are faster [code]O(1)[/code] compared to [Array], but performs worse for random access [code]O(n)[/code]. + [ListNode]s are constructed by inserting values to the list, and are not meant to be instantiated directly: + [codeblock] + var list = LinkedList.new() + var node = list.push_back("Goost") + var same_node = list.find("Goost") + [/codeblock] + Traversing a list can be done using a [code]for[/code] loop: + [codeblock] + for node in list: + print(node) + [/codeblock] + or by manually walking the list using the nodes themselves: + [codeblock] + # Forward! + var node = list.front + while node: + print(node) + node = node.next + + # Backward! + var node = list.back + while node: + print(node) + node = node.prev + [/codeblock] + Nodes can be passed around throughout the code, and values can be changed dynamically for nodes which are already inserted into the list. @@ -11,6 +39,7 @@ + Erases all nodes from the list. @@ -19,12 +48,18 @@ + Initializes the list from a [Variant] compatible type. Clears all nodes before copying. + If [code]value[/code] is [code]null[/code], just clears the contents of the list. + If [code]value[/code] is [Array], each element in the array is converted to a [ListNode]. Pool*Arrays are converted similarly to [Array]. + If [code]value[/code] is [Dictionary], each key in the dictionary is converted to a [ListNode], and the values are encoded as [ListNode] meta variables using [method Object.set_meta]. Values can be retrieved later with [code]node.get_meta("value")[/code] for each node. + Any other type is simply pushed back to the list. + Returns [code]true[/code] if the list doesn't contain any nodes. @@ -33,6 +68,7 @@ + Erases (deletes) the first found node with a matching value in the list. @@ -41,18 +77,21 @@ + Returns a node if a list contains a node with specified value, otherwise returns [code]null[/code]. + An alias for [method get_nodes]. + Returns all nodes as an [Array], preserving front-to-back order. @@ -63,6 +102,7 @@ + Constructs a new [ListNode] and places it [i]after[/i] existing node in the list. If [code]node[/code] is [code]null[/code], then the value is pushed at the end of the list, making the behavior equivalent to [method push_back]. @@ -73,12 +113,14 @@ + Constructs a new [ListNode] and places it [i]before[/i] existing node in the list. If [code]node[/code] is [code]null[/code], then the value is pushed at the end of the list, making the behavior equivalent to [method push_back]. + Inverts the order of nodes in the list. @@ -89,6 +131,7 @@ + Moves a node [i]before[/i] the other one within the list. @@ -97,6 +140,7 @@ + Moves a node to the back of the list ([member back] node will point to [code]node[/code]). @@ -105,18 +149,29 @@ + Moves a node to the front of the list (the [member front] node will point to [code]node[/code]). + Erases the last node of the list. Make sure to preserve the [member ListNode.value] if you're interested in the data associated with the node: + [codeblock] + var value = list.back.value + list.pop_back() + [/codeblock] + Erases the first node of the list. Make sure to preserve the [member ListNode.value] if you're interested in the data associated with the node: + [codeblock] + var value = list.front.value + list.pop_front() + [/codeblock] @@ -125,6 +180,7 @@ + Constructs a new [ListNode] and pushes it at the [i]end[/i] of the list. @@ -133,6 +189,7 @@ + Constructs a new [ListNode] and pushes it at the [i]beginning[/i] of the list. @@ -141,18 +198,21 @@ + Removes (and deletes) an existing node from the list. + Returns the total number of nodes in the list. + Sorts the list in alphabetical order if the list contains [String]s. If the list contains nodes with different types of values, these are sorted according to the order of [enum @GlobalScope.Variant.Type]. @@ -163,13 +223,16 @@ + Moves [code]node_A[/code] to the position of [code]node_B[/code], and moves [code]node_B[/code] to the original position of [code]node_A[/code]. If [code]node_A == node_B[/code], does nothing. + The last node in the list. Can be [code]null[/code] if the list is [method empty]. + The first node in the list. Can be [code]null[/code] if the list is [method empty]. diff --git a/tests/project/goost/core/types/test_list.gd b/tests/project/goost/core/types/test_list.gd index 9e03cb4b..fca37a18 100644 --- a/tests/project/goost/core/types/test_list.gd +++ b/tests/project/goost/core/types/test_list.gd @@ -136,6 +136,21 @@ func test_insert_after_back(): assert_eq(list.back.value, "Godot") +func test_insert_after_null(): + populate_test_data(list) + list.insert_after(null, "Godot") + assert_eq(list.back.value, "Godot") + + +func test_insert_before_null(): + populate_test_data(list) + list.insert_before(null, "Godot") + # Not sure about this, see issue upstream from which this was ported: + # https://github.com/godotengine/godot/issues/42116 + # But consistency with builtin List is more important currently. + assert_eq(list.back.value, "Godot") + + func test_size(): var nodes = populate_test_data(list) var original_size = nodes.size() @@ -443,7 +458,7 @@ func test_custom_iterators(): gut.p(node) -func tets_sort(): +func tets_sort_strings(): var n: ListNode n = list.push_back("B") assert_eq(n.value, "B") @@ -469,6 +484,28 @@ func tets_sort(): assert_eq(abcd[i], abcd_list[i]) +func test_sort_variants(): + list.push_back("Goost") + list.push_back(Color.blue) + list.push_back(37) + list.push_back(Vector2.ONE) + list.push_back(true) + list.push_back(null) + list.push_back(100.0) + + list.sort() + + var nodes = list.get_nodes() + + assert_eq(nodes[0].value, null) + assert_eq(nodes[1].value, true) + assert_eq(nodes[2].value, 37) + assert_eq(nodes[3].value, 100.0) + assert_eq(nodes[4].value, "Goost") + assert_eq(nodes[5].value, Vector2.ONE) + assert_eq(nodes[6].value, Color.blue) + + func test_print_list_node(): var node = list.push_back("Goost") gut.p(node)