summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--compat/intrusive-ptr.hpp326
-rw-r--r--compat/intrusive-ptr.inl8
-rw-r--r--test/app.hpp1
-rw-r--r--test/intrusive-ptr.cpp169
-rw-r--r--test/main.cpp1
5 files changed, 505 insertions, 0 deletions
diff --git a/compat/intrusive-ptr.hpp b/compat/intrusive-ptr.hpp
new file mode 100644
index 00000000..0d09a366
--- /dev/null
+++ b/compat/intrusive-ptr.hpp
@@ -0,0 +1,326 @@
+#pragma once
+#include "compat/assert.hpp"
+#include <compare>
+#include <Corrade/Utility/Move.h>
+
+namespace floormat::iptr { struct non_atomic_u32_tag{}; }
+
+namespace floormat {
+
+template<typename Tag, typename T> class basic_iptr;
+template<typename T> using local_iptr = basic_iptr<iptr::non_atomic_u32_tag, T>;
+
+} // namespace floormat
+
+namespace floormat::iptr {
+
+template<typename Tag, typename T> struct refcount_traits;
+template<typename Tag, typename T> struct refcount_access;
+template<typename Tag, typename T> struct refcount_ops;
+
+template<typename T>
+struct refcount_traits<non_atomic_u32_tag, T>
+{
+ refcount_traits() = delete;
+ using counter_type = uint32_t;
+ using size_type = size_t;
+ using access_t = refcount_access<non_atomic_u32_tag, T>;
+};
+
+template<typename Tag, typename T>
+struct refcount_ops
+{
+ refcount_ops() = delete;
+
+ using traits = refcount_traits<Tag, T>;
+ using counter_type = typename traits::counter_type;
+ using size_type = typename traits::size_type;
+ using access_t = typename traits::access_t;
+
+#ifndef FM_NO_DEBUG
+ using Tref = T*&;
+#else
+ using Tref = T*;
+#endif
+
+ static constexpr inline auto incr(T* ptr) noexcept -> size_type;
+ static constexpr inline auto decr(Tref ptr) noexcept -> size_type;
+ static constexpr inline auto count(T* ptr) noexcept -> size_type;
+ static constexpr inline void init_to_1(T* ptr) noexcept;
+};
+
+template<typename Tag, typename T>
+struct refcount_access
+{
+ using counter_type = refcount_traits<Tag, T>::counter_type;
+ refcount_access() = delete;
+
+ static constexpr auto access(T* ptr) noexcept -> counter_type&;
+ template<typename Y> static constexpr Y* checked_cast(const T* ptr) noexcept; // todo!
+};
+
+template<typename Tag, typename T>
+constexpr auto refcount_ops<Tag, T>::incr(T* ptr) noexcept -> size_type
+{
+ fm_debug_assert(ptr != nullptr);
+ counter_type& ctr{access_t::access(ptr)};
+ fm_debug_assert(size_type{ctr} > size_type{0});
+ size_type ret{++ctr};
+ return ret;
+}
+
+template<typename Tag, typename T>
+constexpr auto refcount_ops<Tag, T>::decr(Tref ptr) noexcept -> size_type
+{
+ static_assert(std::is_nothrow_destructible_v<T>);
+
+ fm_debug_assert(ptr != nullptr);
+ counter_type& ctr{access_t::access(ptr)};
+ fm_debug_assert(size_type{ctr} > size_type{0});
+ size_type ret{--ctr};
+ if (ret == size_type{0})
+ {
+ delete ptr;
+ if constexpr(std::is_reference_v<Tref>)
+ ptr = reinterpret_cast<T*>((uintptr_t)-1);
+ }
+ return ret;
+}
+
+template<typename Tag, typename T>
+constexpr auto refcount_ops<Tag, T>::count(T* ptr) noexcept -> size_type
+{
+ if (!CORRADE_CONSTEVAL)
+ fm_debug_assert((void*)ptr != (void*)-1);
+ if (!ptr) [[unlikely]]
+ return 0;
+ counter_type& ctr{access_t::access(ptr)};
+ fm_debug_assert(size_type{ctr} > size_type{0});
+ size_type ret{ctr};
+ return ret;
+}
+
+template<typename Tag, typename T>
+constexpr void refcount_ops<Tag, T>::init_to_1(T* ptr) noexcept
+{
+ fm_debug_assert(ptr != nullptr);
+ counter_type& ctr{access_t::access(ptr)};
+ fm_debug_assert(size_type{ctr} == size_type{0});
+ ctr = size_type{1};
+}
+
+template<typename Tag, typename T>
+consteval bool check_traits()
+{
+ using traits = ::floormat::iptr::refcount_traits<Tag, T>;
+ using counter_type = typename traits::counter_type;
+ using size_type = typename traits::size_type;
+ using access_t = ::floormat::iptr::refcount_access<Tag, T>;
+ using ops_t = ::floormat::iptr::refcount_ops<Tag, T>;
+
+ static_assert(requires (counter_type& ctr) {
+ requires std::is_arithmetic_v<size_type>;
+ requires std::is_unsigned_v<size_type>;
+ requires std::is_fundamental_v<size_type>;
+ requires noexcept(size_type{ctr});
+ { size_t{size_type{ctr}} };
+ { ctr = size_type{1} };
+ { size_type{++ctr} };
+ { size_type{--ctr} };
+ requires noexcept(size_t{size_type{ctr}});
+ requires noexcept(ctr = size_type{1});
+ requires noexcept(size_type{++ctr});
+ requires noexcept(size_type{--ctr});
+ requires std::is_nothrow_destructible_v<T>;
+ requires sizeof(access_t) != 0;
+ requires sizeof(ops_t) != 0;
+ });
+
+ return true;
+}
+
+} // namespace floormat::iptr
+
+
+
+// ----- macros -----
+
+#define fm_template template<typename Tag, typename T>
+#define fm_basic_iptr basic_iptr<Tag, T>
+
+#ifndef FM_IPTR_USE_CONSTRUCT_AT // todo! finish it
+#define FM_IPTR_USE_CONSTRUCT_AT 0
+#endif
+
+// ----- basic_iptr -----
+
+namespace floormat {
+
+template<typename Tag, typename T>
+class basic_iptr final
+{
+ static_assert(!std::is_reference_v<T>);
+ static_assert(!std::is_const_v<T>); // todo, modify only refcount
+
+ using traits = ::floormat::iptr::refcount_traits<Tag, T>;
+ using counter_type = typename traits::counter_type;
+ using size_type = typename traits::size_type;
+ using access_t = ::floormat::iptr::refcount_access<Tag, T>;
+ using ops_t = ::floormat::iptr::refcount_ops<Tag, T>;
+ static_assert(::floormat::iptr::check_traits<Tag, T>());
+
+ T* _ptr{nullptr};
+
+ // todo use std::construct_at as it has a constexpr exception.
+ // ...but it requires <memory> :( — maybe use an ifdef?
+
+public:
+ constexpr basic_iptr() noexcept;
+ constexpr basic_iptr(std::nullptr_t) noexcept;
+ constexpr basic_iptr& operator=(std::nullptr_t) noexcept;
+ explicit constexpr basic_iptr(T* ptr) noexcept;
+ constexpr basic_iptr(basic_iptr&& other) noexcept;
+ constexpr basic_iptr(const basic_iptr& other) noexcept;
+ constexpr basic_iptr& operator=(basic_iptr&& other) noexcept;
+ constexpr basic_iptr& operator=(const basic_iptr& other) noexcept;
+ constexpr ~basic_iptr() noexcept;
+
+ template<typename... Ts> requires std::is_constructible_v<T, Ts&&...>
+ constexpr basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward)
+ noexcept(std::is_nothrow_constructible_v<T, Ts&&...>);
+
+ constexpr inline void reset() noexcept;
+ constexpr void reset(T* ptr) noexcept; // todo casts
+ constexpr void swap(basic_iptr& other) noexcept;
+ constexpr T* get() const noexcept;
+ constexpr inline T& operator*() const noexcept;
+ constexpr inline T* operator->() const noexcept;
+
+ constexpr size_type use_count() const noexcept;
+ explicit constexpr operator bool() const noexcept;
+
+ template<typename TAG, typename TYPE>
+ friend constexpr bool operator==(const basic_iptr<TAG, TYPE>& a, const basic_iptr<TAG, TYPE>& b) noexcept;
+
+ template<typename TAG, typename TYPE>
+ friend constexpr std::strong_ordering operator<=>(const basic_iptr<TAG, TYPE>& a, const basic_iptr<TAG, TYPE>& b) noexcept;
+};
+
+// ----- constructors -----
+
+template<typename Tag, typename T> constexpr fm_basic_iptr::basic_iptr() noexcept = default;
+template<typename Tag, typename T> constexpr fm_basic_iptr::basic_iptr(std::nullptr_t) noexcept {}
+
+fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(std::nullptr_t) noexcept
+{
+ if (_ptr)
+ {
+ ops_t::decr(_ptr);
+ _ptr = nullptr;
+ }
+ return *this;
+}
+
+fm_template constexpr fm_basic_iptr::basic_iptr(fm_basic_iptr&& other) noexcept: _ptr{other._ptr} { other._ptr = nullptr; }
+
+fm_template constexpr fm_basic_iptr::basic_iptr(const fm_basic_iptr& other) noexcept:
+ _ptr{other._ptr}
+{
+ if (_ptr)
+ ops_t::incr(_ptr);
+}
+
+fm_template constexpr fm_basic_iptr::basic_iptr(T* ptr) noexcept
+{
+ ops_t::init_to_1(ptr);
+}
+
+// ----- destructor -----
+
+fm_template constexpr fm_basic_iptr::~basic_iptr() noexcept { reset(); }
+
+// ----- assignment operators -----
+
+fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(fm_basic_iptr&& other) noexcept
+{
+ if (_ptr != other._ptr)
+ {
+ reset();
+ _ptr = other._ptr;
+ other._ptr = nullptr;
+ }
+ return *this;
+}
+
+fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(const fm_basic_iptr& other) noexcept
+{
+#ifdef __CLION_IDE__
+ if (&other == this)
+ return *this;
+#endif
+ if (other._ptr != _ptr) [[likely]]
+ {
+ if (_ptr)
+ ops_t::decr(_ptr);
+ _ptr = other._ptr;
+ if (_ptr)
+ ops_t::incr(_ptr);
+ }
+ return *this;
+}
+
+fm_template template<typename... Ts>
+requires std::is_constructible_v<T, Ts&&...>
+constexpr fm_basic_iptr::basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward)
+noexcept(std::is_nothrow_constructible_v<T, Ts&&...>):
+ _ptr{new T{Utility::forward<Ts...>(args...)}}
+{
+ ops_t::init_to_1(_ptr);
+}
+
+fm_template constexpr void fm_basic_iptr::reset() noexcept { if (_ptr) ops_t::decr(_ptr); }
+fm_template constexpr void fm_basic_iptr::reset(T* ptr) noexcept { reset(); _ptr = ptr; }
+fm_template constexpr void fm_basic_iptr::swap(basic_iptr& other) noexcept { auto p = _ptr; _ptr = other._ptr; other._ptr = p; }
+
+fm_template constexpr T* fm_basic_iptr::get() const noexcept
+{
+ fm_debug_assert((void*)_ptr != (void*)-1); // NOLINT(*-no-int-to-ptr)
+ return _ptr;
+}
+
+fm_template constexpr T& fm_basic_iptr::operator*() const noexcept { return *get(); }
+fm_template constexpr T* fm_basic_iptr::operator->() const noexcept { return get(); }
+
+fm_template constexpr auto fm_basic_iptr::use_count() const noexcept -> size_type
+{
+ if (!CORRADE_CONSTEVAL)
+ fm_debug_assert((void*)_ptr != (void*)-1);
+ return ops_t::count(_ptr);
+}
+
+fm_template constexpr fm_basic_iptr::operator bool() const noexcept
+{
+ if (!CORRADE_CONSTEVAL)
+ fm_debug_assert((void*)_ptr != (void*)-1);
+ return _ptr != nullptr;
+}
+
+fm_template constexpr bool operator==(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept
+{
+ if (!CORRADE_CONSTEVAL)
+ {
+ fm_debug_assert((void*)a._ptr != (void*)-1);
+ fm_debug_assert((void*)b._ptr != (void*)-1);
+ }
+ return a._ptr == b._ptr;
+}
+
+fm_template constexpr std::strong_ordering operator<=>(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept
+{
+ return a._ptr <=> b._ptr;
+}
+
+#undef fm_template
+#undef fm_basic_iptr
+
+} // namespace floormat
diff --git a/compat/intrusive-ptr.inl b/compat/intrusive-ptr.inl
new file mode 100644
index 00000000..b8d40e3b
--- /dev/null
+++ b/compat/intrusive-ptr.inl
@@ -0,0 +1,8 @@
+#pragma once
+#include "intrusive-ptr.hpp"
+
+namespace floormat {
+
+
+
+} // namespace floormat
diff --git a/test/app.hpp b/test/app.hpp
index fc0bc90c..78794cd3 100644
--- a/test/app.hpp
+++ b/test/app.hpp
@@ -39,6 +39,7 @@ struct test_app final : private FM_APPLICATION
static void test_wall_atlas();
static void test_wall_atlas2();
static void test_raycast();
+ static void test_intrusive_ptr();
static void zzz_test_misc();
};
} // namespace floormat
diff --git a/test/intrusive-ptr.cpp b/test/intrusive-ptr.cpp
new file mode 100644
index 00000000..c37d5b2b
--- /dev/null
+++ b/test/intrusive-ptr.cpp
@@ -0,0 +1,169 @@
+#include "app.hpp"
+#include "compat/intrusive-ptr.hpp"
+#include "compat/defs.hpp"
+
+#ifdef __CLION_IDE__
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma ide diagnostic ignored "performance-unnecessary-copy-initialization"
+#endif
+
+namespace floormat {
+
+namespace { struct Test2 { int val = 0; uint32_t _counter = 0; }; }
+
+template<>
+constexpr auto
+iptr::refcount_access<iptr::non_atomic_u32_tag, Test2>::access(Test2* ptr) noexcept -> counter_type&
+{
+ return ptr->_counter;
+}
+
+namespace {
+
+struct non_copyable
+{
+ int value = 0;
+ explicit constexpr non_copyable(DirectInitT) {}
+ explicit constexpr non_copyable(DirectInitT, int value) : value{value} {}
+ fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT(non_copyable);
+ fm_DECLARE_DELETED_COPY_ASSIGNMENT(non_copyable);
+};
+
+struct S { // todo fm_assert_equal
+ int instances;
+ unsigned allocs, frees;
+ bool operator==(const S&) const = default;
+} s = {};
+
+struct Test1
+{
+ friend struct iptr::refcount_access<iptr::non_atomic_u32_tag, Test1>;
+ Test1(non_copyable nc) noexcept : value{nc.value} { s.instances++; s.allocs++; }
+ ~Test1() noexcept { s.instances--; fm_assert(s.instances >= 0); s.frees++; }
+
+ int value = 0;
+
+private:
+ iptr::refcount_traits<iptr::non_atomic_u32_tag, Test1>::counter_type counter{0};
+};
+
+} // namespace
+
+template<>
+constexpr auto iptr::refcount_access<iptr::non_atomic_u32_tag, Test1>::access(Test1* ptr) noexcept -> counter_type&
+{
+ return ptr->counter;
+}
+
+template class basic_iptr<iptr::non_atomic_u32_tag, Test1>;
+
+namespace {
+
+void test_copy()
+{
+ using myptr = local_iptr<Test1>;
+
+ s = {};
+
+ { (void)myptr{nullptr};
+ fm_assert(s == S{});
+ (void)myptr{};
+ fm_assert(s == S{});
+ { Test1 t1{non_copyable{DirectInit}}; }
+ fm_assert(s != S{});
+ fm_assert(s == S{0, 1, 1});
+
+ { Test1 t1{non_copyable{DirectInit}};
+ fm_assert(s == S{1, 2, 1});
+ } fm_assert(s == S{0, 2, 2});
+ }
+
+ { auto a = myptr{InPlaceInit, non_copyable{DirectInit}};
+ fm_assert(s == S{1, 3, 2});
+ auto b = a;
+ fm_assert(s == S{1, 3, 2});
+ fm_assert(b.get() == a.get());
+ } fm_assert(s == S{0, 3, 3});
+
+ { auto a = myptr{InPlaceInit, non_copyable{DirectInit}};
+ fm_assert(s == S{1, 4, 3});
+ a = {};
+ fm_assert(s == S{0, 4, 4});
+ } fm_assert(s == S{0, 4, 4});
+
+ { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}};
+ auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}};
+ { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}};
+ auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}};
+ }
+ fm_assert(a.use_count() == 1);
+ fm_assert(a->value == 1);
+ fm_assert(b->value == 2);
+ fm_assert(s == S{2, 8, 6});
+ b = a;
+ fm_assert(b.use_count() == 2);
+ fm_assert(a.use_count() == 2);
+ b = nullptr;
+ fm_assert(b.use_count() == 0);
+ fm_assert(a.use_count() == 1);
+ fm_assert(s == S{1, 8, 7});
+ fm_assert(a->value == 1);
+ } fm_assert(s == S{0, 8, 8});
+}
+
+#define fm_assert_free() do { fm_assert(s.allocs == s.frees); fm_assert(s.instances == 0); } while (false)
+
+void test_move()
+{
+ using myptr = local_iptr<Test1>;
+ using Utility::move;
+
+ fm_assert_free();
+ s = {};
+
+ { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}};
+ auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}};
+ fm_assert(s == S{2, 2, 0});
+ { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}};
+ auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}};
+ fm_assert(s == S{4, 4, 0});
+ } fm_assert(s == S{2, 4, 2});
+
+ { a = move(b);
+ fm_assert(s == S{1, 4, 3});
+ fm_assert(!b);
+ fm_assert(a);
+ fm_assert(b.use_count() == 0);
+ fm_assert(a.use_count() == 1);
+ }
+ } fm_assert(s == S{0, 4, 4});
+}
+
+constexpr bool test_cexpr() // todo
+{
+ using myptr = local_iptr<Test2>;
+ using Utility::move;
+
+ // construct
+ auto foo1 = myptr{};
+ auto foo2 = myptr{nullptr};
+
+ fm_assert(foo1.use_count() == 0);
+ fm_assert(foo2.use_count() == 0);
+ foo1 = move(foo2);
+ fm_assert(foo1.use_count() == 0);
+ fm_assert(foo2.use_count() == 0);
+
+ return true;
+}
+
+} // namespace
+
+void test_app::test_iptr()
+{
+ static_assert(test_cexpr());
+ test_copy();
+ test_move();
+}
+
+} // namespace floormat
diff --git a/test/main.cpp b/test/main.cpp
index 3b77de46..7eb2b6af 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -28,6 +28,7 @@ int test_app::exec()
test_entity();
test_math();
test_hash();
+ test_intrusive_ptr();
test_loader();
test_bitmask();
test_wall_atlas();