Add Irrlicht-specific smart pointer (#6814)
authorVitaliy <numzer0@yandex.ru>
Fri, 12 Apr 2019 16:27:39 +0000 (19:27 +0300)
committerrubenwardy <rw@rubenwardy.com>
Fri, 12 Apr 2019 16:27:39 +0000 (17:27 +0100)
src/irr_ptr.h [new file with mode: 0644]
src/unittest/CMakeLists.txt
src/unittest/test_irrptr.cpp [new file with mode: 0644]

diff --git a/src/irr_ptr.h b/src/irr_ptr.h
new file mode 100644 (file)
index 0000000..5022adb
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+Minetest
+Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+#include <type_traits>
+#include "irrlichttypes.h"
+#include "IReferenceCounted.h"
+
+/** Shared pointer for IrrLicht objects.
+ *
+ * It should only be used for user-managed objects, i.e. those created with
+ * the @c new operator or @c create* functions, like:
+ * `irr_ptr<scene::IMeshBuffer> buf{new scene::SMeshBuffer()};`
+ *
+ * It should *never* be used for engine-managed objects, including
+ * those created with @c addTexture and similar methods.
+ */
+template <class ReferenceCounted,
+               class = typename std::enable_if<std::is_base_of<IReferenceCounted,
+                               ReferenceCounted>::value>::type>
+class irr_ptr
+{
+       ReferenceCounted *value = nullptr;
+
+       /** Drops stored pointer replacing it with the given one.
+        * @note Copy semantics: reference counter *is* increased.
+        */
+       void grab(ReferenceCounted *object)
+       {
+               if (object)
+                       object->grab();
+               reset(object);
+       }
+
+public:
+       irr_ptr() {}
+
+       irr_ptr(std::nullptr_t) noexcept {}
+
+       irr_ptr(const irr_ptr &b) noexcept { grab(b.get()); }
+
+       irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); }
+
+       template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+                                             ReferenceCounted *>::value>::type>
+       irr_ptr(const irr_ptr<B> &b) noexcept
+       {
+               grab(b.get());
+       }
+
+       template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+                                             ReferenceCounted *>::value>::type>
+       irr_ptr(irr_ptr<B> &&b) noexcept
+       {
+               reset(b.release());
+       }
+
+       /** Constructs a shared pointer out of a plain one
+        * @note Move semantics: reference counter is *not* increased.
+        */
+       explicit irr_ptr(ReferenceCounted *object) noexcept { reset(object); }
+
+       ~irr_ptr() { reset(); }
+
+       irr_ptr &operator=(const irr_ptr &b) noexcept
+       {
+               grab(b.get());
+               return *this;
+       }
+
+       irr_ptr &operator=(irr_ptr &&b) noexcept
+       {
+               reset(b.release());
+               return *this;
+       }
+
+       template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+                                             ReferenceCounted *>::value>::type>
+       irr_ptr &operator=(const irr_ptr<B> &b) noexcept
+       {
+               grab(b.get());
+               return *this;
+       }
+
+       template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+                                             ReferenceCounted *>::value>::type>
+       irr_ptr &operator=(irr_ptr<B> &&b) noexcept
+       {
+               reset(b.release());
+               return *this;
+       }
+
+       ReferenceCounted &operator*() const noexcept { return *value; }
+       ReferenceCounted *operator->() const noexcept { return value; }
+       explicit operator ReferenceCounted *() const noexcept { return value; }
+       explicit operator bool() const noexcept { return !!value; }
+
+       /** Returns the stored pointer.
+        */
+       ReferenceCounted *get() const noexcept { return value; }
+
+       /** Returns the stored pointer, erasing it from this class.
+        * @note Move semantics: reference counter is not changed.
+        */
+       ReferenceCounted *release() noexcept
+       {
+               ReferenceCounted *object = value;
+               value = nullptr;
+               return object;
+       }
+
+       /** Drops stored pointer replacing it with the given one.
+        * @note Move semantics: reference counter is *not* increased.
+        */
+       void reset(ReferenceCounted *object = nullptr) noexcept
+       {
+               if (value)
+                       value->drop();
+               value = object;
+       }
+};
index 71aa1fa567bd4f4e3299b9baee550078bdaf9895..82f9a4a139b10f156a24124a62321c6cae10d702 100644 (file)
@@ -10,6 +10,7 @@ set (UNITTEST_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp
diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp
new file mode 100644 (file)
index 0000000..aa857ff
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+Minetest
+Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "test.h"
+
+#include "exceptions.h"
+#include "irr_ptr.h"
+
+class TestIrrPtr : public TestBase
+{
+public:
+       TestIrrPtr() { TestManager::registerTestModule(this); }
+       const char *getName() { return "TestIrrPtr"; }
+
+       void runTests(IGameDef *gamedef);
+
+       void testRefCounting();
+       void testSelfAssignment();
+       void testNullHandling();
+};
+
+static TestIrrPtr g_test_instance;
+
+void TestIrrPtr::runTests(IGameDef *gamedef)
+{
+       TEST(testRefCounting);
+       TEST(testSelfAssignment);
+       TEST(testNullHandling);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define UASSERT_REFERENCE_COUNT(object, value, info)                                     \
+       UTEST((object)->getReferenceCount() == value,                                    \
+                       info "Reference count is %d instead of " #value,                 \
+                       (object)->getReferenceCount())
+
+void TestIrrPtr::testRefCounting()
+{
+       IReferenceCounted *obj = new IReferenceCounted(); // RC=1
+       obj->grab();
+       UASSERT_REFERENCE_COUNT(obj, 2, "Pre-condition failed: ");
+       {
+               irr_ptr<IReferenceCounted> p1{obj}; // move semantics
+               UASSERT(p1.get() == obj);
+               UASSERT_REFERENCE_COUNT(obj, 2, );
+
+               irr_ptr<IReferenceCounted> p2{p1}; // copy ctor
+               UASSERT(p1.get() == obj);
+               UASSERT(p2.get() == obj);
+               UASSERT_REFERENCE_COUNT(obj, 3, );
+
+               irr_ptr<IReferenceCounted> p3{std::move(p1)}; // move ctor
+               UASSERT(p1.get() == nullptr);
+               UASSERT(p3.get() == obj);
+               UASSERT_REFERENCE_COUNT(obj, 3, );
+
+               p1 = std::move(p2); // move assignment
+               UASSERT(p1.get() == obj);
+               UASSERT(p2.get() == nullptr);
+               UASSERT_REFERENCE_COUNT(obj, 3, );
+
+               p2 = p3; // copy assignment
+               UASSERT(p2.get() == obj);
+               UASSERT(p3.get() == obj);
+               UASSERT_REFERENCE_COUNT(obj, 4, );
+
+               p1.release();
+               UASSERT(p1.get() == nullptr);
+               UASSERT_REFERENCE_COUNT(obj, 4, );
+       }
+       UASSERT_REFERENCE_COUNT(obj, 2, );
+       obj->drop();
+       UTEST(obj->drop(), "Dropping failed: reference count is %d",
+                       obj->getReferenceCount());
+}
+
+void TestIrrPtr::testSelfAssignment()
+{
+       irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
+       UASSERT(p1);
+       UASSERT_REFERENCE_COUNT(p1, 1, );
+       p1 = p1;
+       UASSERT(p1);
+       UASSERT_REFERENCE_COUNT(p1, 1, );
+       p1 = std::move(p1);
+       UASSERT(p1);
+       UASSERT_REFERENCE_COUNT(p1, 1, );
+}
+
+void TestIrrPtr::testNullHandling()
+{
+       // In the case of an error, it will probably crash with SEGV.
+       // Nevertheless, UASSERTs are used to catch possible corner cases.
+       irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
+       UASSERT(p1);
+       irr_ptr<IReferenceCounted> p2;
+       UASSERT(!p2);
+       irr_ptr<IReferenceCounted> p3{p2};
+       UASSERT(!p2);
+       UASSERT(!p3);
+       irr_ptr<IReferenceCounted> p4{std::move(p2)};
+       UASSERT(!p2);
+       UASSERT(!p4);
+       p2 = p2;
+       UASSERT(!p2);
+       p2 = std::move(p2);
+       UASSERT(!p2);
+       p3 = p2;
+       UASSERT(!p2);
+       UASSERT(!p3);
+       p3 = std::move(p2);
+       UASSERT(!p2);
+       UASSERT(!p3);
+}