Browse Source

Add my own implementation of std::function

Panagiotis Christopoulos Charitos 4 years ago
parent
commit
16a12ba5bf
4 changed files with 275 additions and 0 deletions
  1. 1 0
      AnKi/Util.h
  2. 3 0
      AnKi/Util/Forward.h
  3. 209 0
      AnKi/Util/Function.h
  4. 62 0
      Tests/Util/Function.cpp

+ 1 - 0
AnKi/Util.h

@@ -41,6 +41,7 @@
 #include <AnKi/Util/Serializer.h>
 #include <AnKi/Util/Serializer.h>
 #include <AnKi/Util/Xml.h>
 #include <AnKi/Util/Xml.h>
 #include <AnKi/Util/F16.h>
 #include <AnKi/Util/F16.h>
+#include <AnKi/Util/Function.h>
 
 
 /// @defgroup util Utilities (like STL)
 /// @defgroup util Utilities (like STL)
 
 

+ 3 - 0
AnKi/Util/Forward.h

@@ -38,4 +38,7 @@ class StringAuto;
 
 
 class ThreadHive;
 class ThreadHive;
 
 
+template<typename T, PtrSize T_PREALLOCATED_STORAGE = ANKI_SAFE_ALIGNMENT>
+class Function;
+
 } // end namespace anki
 } // end namespace anki

+ 209 - 0
AnKi/Util/Function.h

@@ -0,0 +1,209 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Util/Allocator.h>
+#include <AnKi/Util/Forward.h>
+#include <AnKi/Util/Array.h>
+
+namespace anki
+{
+
+/// @addtogroup util_containers
+/// @{
+
+/// This is almost like a re-implementation of std::function. The difference is that it has some functionality to avoid
+/// allocations. Can be used like:
+/// @code
+/// Function<Error(U32, F32), 16> func;
+/// func.init(allocator, [&someInt](U32 u, F32 f) {someInt = xxx + u + f; return Error::NONE;});
+/// func.call(10, 1.2f);
+/// @endcode
+/// @tparam T_INLINE_STORAGE_SIZE Optional inline storage to avoid deallocations (small object optimization)
+template<typename TReturn, typename... TArgs, PtrSize T_INLINE_STORAGE_SIZE>
+class Function<TReturn(TArgs...), T_INLINE_STORAGE_SIZE>
+{
+public:
+	Function() = default;
+
+	/// Move.
+	Function(Function&& b)
+	{
+		*this = std::move(b);
+	}
+
+	// Non-copyable.
+	Function(const Function&) = delete;
+
+	/// Same as init().
+	template<typename TAlloc, typename T>
+	Function(TAlloc alloc, const T& func)
+	{
+		init(alloc, func);
+	}
+
+	// Does nothing important.
+	~Function()
+	{
+		ANKI_ASSERT(getState() == STATE_UNINITIALIZED && "Forgot to call destroy()");
+	}
+
+	// Non-copyable.
+	Function& operator=(const Function&) = delete;
+
+	/// Move.
+	Function& operator=(Function&& b)
+	{
+		ANKI_ASSERT(getState() == STATE_UNINITIALIZED);
+		m_state = b.m_state;
+		b.m_state = STATE_UNINITIALIZED;
+		memcpy(&m_callableInlineStorage[0], &b.m_callableInlineStorage[0], sizeof(m_callableInlineStorage));
+		m_destroyCallback = b.m_destroyCallback;
+		b.m_destroyCallback = nullptr;
+		return *this;
+	}
+
+	/// Initialize the function.
+	/// @param alloc The allocator (it might be used).
+	/// @param func The lambda.
+	template<typename TAlloc, typename T>
+	void init(TAlloc alloc, const T& func)
+	{
+		ANKI_ASSERT(getState() == STATE_UNINITIALIZED);
+
+		// Init storage
+		const Bool useInlineStorage = sizeof(T) <= INLINE_STORAGE_SIZE && std::is_trivially_copyable<T>::value;
+		if(useInlineStorage)
+		{
+			setState(STATE_INLINE_STORAGE);
+			memcpy(&m_callableInlineStorage[0], &func, sizeof(func));
+
+			setFunctionCallback([](Function& self, TArgs... args) -> TReturn {
+				return (*reinterpret_cast<T*>(&self.m_callableInlineStorage[0]))(args...);
+			});
+
+			if(!std::is_trivially_destructible<T>::value)
+			{
+				m_destroyCallback = [](Function& self) {
+					reinterpret_cast<T*>(&self.m_callableInlineStorage[0])->~T();
+				};
+			}
+			else
+			{
+				m_destroyCallback = nullptr;
+			}
+		}
+		else
+		{
+			setState(STATE_ALLOCATED);
+			m_callablePtr = alloc.template newInstance<T>(func);
+
+			setFunctionCallback([](Function& self, TArgs... args) -> TReturn {
+				return (*static_cast<T*>(self.m_callablePtr))(args...);
+			});
+
+			if(!std::is_trivially_destructible<T>::value)
+			{
+				m_destroyCallback = [](Function& self) { static_cast<T*>(self.m_callablePtr)->~T(); };
+			}
+			else
+			{
+				m_destroyCallback = nullptr;
+			}
+		}
+	}
+
+	/// Destroy the object.
+	template<typename TAlloc>
+	void destroy(TAlloc alloc)
+	{
+		if(m_destroyCallback)
+		{
+			m_destroyCallback(*this);
+		}
+
+		if(getState() == STATE_ALLOCATED)
+		{
+			alloc.deallocate(m_callablePtr, 1);
+		}
+
+		m_state = STATE_UNINITIALIZED;
+		m_destroyCallback = nullptr;
+	}
+
+	/// Call the Function with some arguments.
+	TReturn call(TArgs... args)
+	{
+		return getFunctionCallback()(*this, args...);
+	}
+
+	/// Same as call().
+	TReturn operator()(TArgs... args)
+	{
+		return getFunctionCallback()(*this, args...);
+	}
+
+private:
+	using FunctionCallback = TReturn (*)(Function&, TArgs...);
+	using DestroyCallback = void (*)(Function&);
+
+	static constexpr PtrSize STATE_UNINITIALIZED = PtrSize(0b1001) << PtrSize(60);
+	static constexpr PtrSize STATE_ALLOCATED = PtrSize(0b1101) << PtrSize(60);
+	static constexpr PtrSize STATE_INLINE_STORAGE = PtrSize(0b1011) << PtrSize(60);
+	static constexpr PtrSize STATE_ALL_BITS = PtrSize(0b1111) << PtrSize(60);
+	static_assert(sizeof(void*) == 8, "Wrong assumption");
+
+	static constexpr PtrSize lmax(PtrSize a, PtrSize b)
+	{
+		return (a > b) ? a : b;
+	}
+
+	static constexpr PtrSize INLINE_STORAGE_SIZE =
+		lmax(T_INLINE_STORAGE_SIZE, lmax(ANKI_SAFE_ALIGNMENT, sizeof(void*)));
+
+	union
+	{
+		void* m_callablePtr;
+		alignas(ANKI_SAFE_ALIGNMENT) Array<U8, INLINE_STORAGE_SIZE> m_callableInlineStorage;
+	};
+
+	union
+	{
+		// Hide the state in the high bits of the m_functionCallback pointer.
+		PtrSize m_state = STATE_UNINITIALIZED;
+
+		FunctionCallback m_functionCallback;
+	};
+
+	DestroyCallback m_destroyCallback = nullptr;
+
+	PtrSize getState() const
+	{
+		const PtrSize s = m_state & STATE_ALL_BITS;
+		ANKI_ASSERT(s == STATE_UNINITIALIZED || s == STATE_ALLOCATED || s == STATE_INLINE_STORAGE);
+		return s;
+	}
+
+	void setState(PtrSize s)
+	{
+		ANKI_ASSERT(s == STATE_UNINITIALIZED || s == STATE_ALLOCATED || s == STATE_INLINE_STORAGE);
+		m_state = (m_state & ~STATE_ALL_BITS) | s;
+	}
+
+	FunctionCallback getFunctionCallback() const
+	{
+		return numberToPtr<FunctionCallback>(m_state & ~STATE_ALL_BITS);
+	}
+
+	void setFunctionCallback(FunctionCallback f)
+	{
+		m_state = (m_state & STATE_ALL_BITS) | ptrToNumber(f);
+		ANKI_ASSERT(f == getFunctionCallback());
+	}
+};
+/// @}
+
+} // end namespace anki

+ 62 - 0
Tests/Util/Function.cpp

@@ -0,0 +1,62 @@
+// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <Tests/Framework/Framework.h>
+#include <Tests/Util/Foo.h>
+#include <AnKi/Util/Function.h>
+
+ANKI_TEST(Util, Function)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Simple
+	{
+		Function<I32(I32, F32), 8> f;
+
+		I32 i = 0;
+		f.init(alloc, [&i](U32 a, F32 b) -> I32 {
+			i += I32(a) + I32(b);
+			return i;
+		});
+
+		i = 1;
+
+		f(2, 10.0f);
+		ANKI_TEST_EXPECT_EQ(i, 13);
+
+		f.destroy(alloc);
+	}
+
+	// Allocated
+	{
+		const Vec4 a(1.9f);
+		const Vec4 b(2.2f);
+		Function<Vec4(Vec4, Vec4), 8> f(alloc, [a, b](Vec4 c, Vec4 d) mutable -> Vec4 { return a + c * 2.0f + b * d; });
+
+		const Vec4 r = f(Vec4(10.0f), Vec4(20.8f));
+		ANKI_TEST_EXPECT_EQ(r, a + Vec4(10.0f) * 2.0f + b * Vec4(20.8f));
+
+		f.destroy(alloc);
+	}
+
+	// Complex
+	{
+		{
+			Foo foo, bar;
+			Function<void(Foo&)> f(alloc, [foo](Foo& r) { r.x += foo.x; });
+
+			Function<void(Foo&)> ff;
+			ff = std::move(f);
+
+			ff(bar);
+
+			ANKI_TEST_EXPECT_EQ(bar.x, 666 * 2);
+			ff.destroy(alloc);
+		}
+
+		ANKI_TEST_EXPECT_EQ(Foo::constructorCallCount, Foo::destructorCallCount);
+		Foo::reset();
+	}
+}