|
@@ -33,11 +33,9 @@
|
|
|
|
|
|
#include "core/os/memory.h"
|
|
|
#include "core/typedefs.h"
|
|
|
-#include <functional>
|
|
|
-
|
|
|
-#if !defined(NO_THREADS)
|
|
|
|
|
|
#include <atomic>
|
|
|
+#include <functional>
|
|
|
#include <type_traits>
|
|
|
|
|
|
// Design goals for these classes:
|
|
@@ -239,159 +237,4 @@ public:
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-#else // NO_THREADS
|
|
|
-
|
|
|
-// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly.
|
|
|
-template <class T, class A = DefaultAllocator>
|
|
|
-class SafeList {
|
|
|
- struct SafeListNode {
|
|
|
- SafeListNode *next = nullptr;
|
|
|
-
|
|
|
- // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted.
|
|
|
- SafeListNode *graveyard_next = nullptr;
|
|
|
-
|
|
|
- std::function<void(T)> deletion_fn = [](T t) { return; };
|
|
|
-
|
|
|
- T val;
|
|
|
- };
|
|
|
-
|
|
|
- SafeListNode *head = nullptr;
|
|
|
- SafeListNode *graveyard_head = nullptr;
|
|
|
-
|
|
|
- unsigned int active_iterator_count = 0;
|
|
|
-
|
|
|
-public:
|
|
|
- class Iterator {
|
|
|
- friend class SafeList;
|
|
|
-
|
|
|
- SafeListNode *cursor = nullptr;
|
|
|
- SafeList *list = nullptr;
|
|
|
-
|
|
|
- public:
|
|
|
- Iterator(SafeListNode *p_cursor, SafeList *p_list) :
|
|
|
- cursor(p_cursor), list(p_list) {
|
|
|
- list->active_iterator_count++;
|
|
|
- }
|
|
|
-
|
|
|
- ~Iterator() {
|
|
|
- list->active_iterator_count--;
|
|
|
- }
|
|
|
-
|
|
|
- T &operator*() {
|
|
|
- return cursor->val;
|
|
|
- }
|
|
|
-
|
|
|
- Iterator &operator++() {
|
|
|
- cursor = cursor->next;
|
|
|
- return *this;
|
|
|
- }
|
|
|
-
|
|
|
- // These two operators are mostly useful for comparisons to nullptr.
|
|
|
- bool operator==(const void *p_other) const {
|
|
|
- return cursor == p_other;
|
|
|
- }
|
|
|
-
|
|
|
- bool operator!=(const void *p_other) const {
|
|
|
- return cursor != p_other;
|
|
|
- }
|
|
|
-
|
|
|
- // These two allow easy range-based for loops.
|
|
|
- bool operator==(const Iterator &p_other) const {
|
|
|
- return cursor == p_other.cursor;
|
|
|
- }
|
|
|
-
|
|
|
- bool operator!=(const Iterator &p_other) const {
|
|
|
- return cursor != p_other.cursor;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
-public:
|
|
|
- // Calling this will cause an allocation.
|
|
|
- void insert(T p_value) {
|
|
|
- SafeListNode *new_node = memnew_allocator(SafeListNode, A);
|
|
|
- new_node->val = p_value;
|
|
|
- new_node->next = head;
|
|
|
- head = new_node;
|
|
|
- }
|
|
|
-
|
|
|
- Iterator find(T p_value) {
|
|
|
- for (Iterator it = begin(); it != end(); ++it) {
|
|
|
- if (*it == p_value) {
|
|
|
- return it;
|
|
|
- }
|
|
|
- }
|
|
|
- return end();
|
|
|
- }
|
|
|
-
|
|
|
- void erase(T p_value, std::function<void(T)> p_deletion_fn) {
|
|
|
- erase(find(p_value), p_deletion_fn);
|
|
|
- }
|
|
|
-
|
|
|
- void erase(T p_value) {
|
|
|
- erase(find(p_value), [](T t) { return; });
|
|
|
- }
|
|
|
-
|
|
|
- void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) {
|
|
|
- p_iterator.cursor->deletion_fn = p_deletion_fn;
|
|
|
- erase(p_iterator);
|
|
|
- }
|
|
|
-
|
|
|
- void erase(Iterator p_iterator) {
|
|
|
- Iterator prev = begin();
|
|
|
- for (; prev != end(); ++prev) {
|
|
|
- if (prev.cursor && prev.cursor->next == p_iterator.cursor) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (prev == end()) {
|
|
|
- // Not in the list, nothing to do.
|
|
|
- return;
|
|
|
- }
|
|
|
- // First, remove the node from the list.
|
|
|
- prev.cursor->next = p_iterator.cursor->next;
|
|
|
-
|
|
|
- // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node.
|
|
|
- p_iterator.cursor->graveyard_next = graveyard_head;
|
|
|
- graveyard_head = p_iterator.cursor;
|
|
|
- }
|
|
|
-
|
|
|
- Iterator begin() {
|
|
|
- return Iterator(head, this);
|
|
|
- }
|
|
|
-
|
|
|
- Iterator end() {
|
|
|
- return Iterator(nullptr, this);
|
|
|
- }
|
|
|
-
|
|
|
- // Calling this will cause zero to many deallocations.
|
|
|
- bool maybe_cleanup() {
|
|
|
- SafeListNode *cursor = graveyard_head;
|
|
|
- if (active_iterator_count != 0) {
|
|
|
- // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete.
|
|
|
- return false;
|
|
|
- }
|
|
|
- graveyard_head = nullptr;
|
|
|
- // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion.
|
|
|
- while (cursor) {
|
|
|
- SafeListNode *tmp = cursor;
|
|
|
- cursor = cursor->next;
|
|
|
- tmp->deletion_fn(tmp->val);
|
|
|
- memdelete_allocator<SafeListNode, A>(tmp);
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- ~SafeList() {
|
|
|
-#ifdef DEBUG_ENABLED
|
|
|
- if (!maybe_cleanup()) {
|
|
|
- ERR_PRINT("There are still iterators around when destructing a SafeList. Memory will be leaked. This is a bug.");
|
|
|
- }
|
|
|
-#else
|
|
|
- maybe_cleanup();
|
|
|
-#endif
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-#endif
|
|
|
-
|
|
|
#endif // SAFE_LIST_H
|