Browse Source

Adding the async resource loader with unit tests

Panagiotis Christopoulos Charitos 11 years ago
parent
commit
6004fac118

+ 91 - 0
include/anki/resource/AsyncLoader.h

@@ -0,0 +1,91 @@
+// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#ifndef ANKI_RESOURCE_ASYNC_LOADER_H
+#define ANKI_RESOURCE_ASYNC_LOADER_H
+
+#include "anki/resource/Common.h"
+#include "anki/util/Thread.h"
+
+namespace anki {
+
+/// @addtogroup resource
+/// @{
+
+/// Asynchronous resource loader.
+class AsyncLoader
+{
+public:
+	/// Loader asynchronous task.
+	class Task
+	{
+		friend class AsyncLoader;
+
+	public:
+		virtual ~Task()
+		{}
+
+		virtual void operator()() = 0;
+
+	private:
+		Task* m_next = nullptr;
+	};
+
+	AsyncLoader(const HeapAllocator<U8>& alloc);
+
+	~AsyncLoader();
+
+	/// Create a new asynchronous loading task.
+	template<typename TTask, typename... TArgs>
+	void newTask(TArgs&&... args)
+	{
+		TTask* newTask = m_alloc.template newInstance<TTask>(
+			std::forward<TArgs>(args)...);
+
+		// Append task to the list
+		{
+			LockGuard<Mutex> lock(m_mtx);
+			if(m_tail != nullptr)
+			{
+				ANKI_ASSERT(m_tail->m_next == nullptr);
+				ANKI_ASSERT(m_head != nullptr);
+
+				m_tail->m_next = newTask;
+				m_tail = newTask;
+			}
+			else
+			{
+				ANKI_ASSERT(m_head == nullptr);
+				m_head = m_tail = newTask;
+			}
+		}
+
+		// Wake up the thread
+		m_condVar.notifyOne();
+	}
+
+private:
+	HeapAllocator<U8> m_alloc;
+	Thread m_thread;
+	Mutex m_mtx;
+	ConditionVariable m_condVar;
+	Task* m_head = nullptr;
+	Task* m_tail = nullptr;
+	Bool8 m_quit = false;
+
+	/// Thread callback
+	static I threadCallback(Thread::Info& info);
+
+	void threadWorker();
+
+	void stop();
+};
+
+/// @}
+
+} // end namespace anki
+
+#endif
+

+ 7 - 0
include/anki/resource/ResourceManager.h

@@ -18,6 +18,7 @@ namespace anki {
 class ConfigSet;
 class ConfigSet;
 class GlDevice;
 class GlDevice;
 class ResourceManager;
 class ResourceManager;
+class AsyncLoader;
 
 
 // NOTE: Add resources in 3 places
 // NOTE: Add resources in 3 places
 #define ANKI_RESOURCE(rsrc_, name_) \
 #define ANKI_RESOURCE(rsrc_, name_) \
@@ -220,6 +221,11 @@ public:
 	{
 	{
 		TypeResourceManager<T, ResourceManager>::_unregisterResource(ptr);
 		TypeResourceManager<T, ResourceManager>::_unregisterResource(ptr);
 	}
 	}
+
+	AsyncLoader& _getAsyncLoader() 
+	{
+		return *m_asyncLoader;
+	}
 	/// @}
 	/// @}
 
 
 private:
 private:
@@ -231,6 +237,7 @@ private:
 	U32 m_maxTextureSize;
 	U32 m_maxTextureSize;
 	U32 m_textureAnisotropy;
 	U32 m_textureAnisotropy;
 	ResourceString m_shadersPrependedSource;
 	ResourceString m_shadersPrependedSource;
+	AsyncLoader* m_asyncLoader = nullptr; ///< Async loading thread
 };
 };
 
 
 #undef ANKI_RESOURCE
 #undef ANKI_RESOURCE

+ 107 - 0
src/resource/AsyncLoader.cpp

@@ -0,0 +1,107 @@
+// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "anki/resource/AsyncLoader.h"
+#include "anki/core/Logger.h"
+
+namespace anki {
+
+//==============================================================================
+AsyncLoader::AsyncLoader(const HeapAllocator<U8>& alloc)
+:	m_alloc(alloc),
+	m_thread("anki_async_loader")
+{
+	m_thread.start(this, threadCallback);
+}
+
+//==============================================================================
+AsyncLoader::~AsyncLoader()
+{
+	stop();
+
+	if(m_head != nullptr)
+	{
+		ANKI_ASSERT(m_tail != nullptr);
+		ANKI_LOGW("Stoping loading thread while there is work to do");
+
+		Task* task = m_head;
+
+		do
+		{
+			Task* next = task->m_next;
+			m_alloc.deleteInstance(task);
+			task = next;
+		}while(task != nullptr);
+	}
+}
+
+//==============================================================================
+void AsyncLoader::stop()
+{
+	{
+		LockGuard<Mutex> lock(m_mtx);
+		m_quit = true;
+	}
+
+	m_condVar.notifyOne();
+	m_thread.join();
+}
+
+//==============================================================================
+I AsyncLoader::threadCallback(Thread::Info& info)
+{
+	AsyncLoader& self = 
+		*reinterpret_cast<AsyncLoader*>(info.m_userData);
+
+	self.threadWorker();
+
+	return 0;
+}
+
+//==============================================================================
+void AsyncLoader::threadWorker()
+{
+	while(true)
+	{
+		Task* task;
+
+		{
+			// Wait for something
+			LockGuard<Mutex> lock(m_mtx);
+			while(m_head == nullptr && m_quit == false)
+			{
+				m_condVar.wait(m_mtx);
+			}
+
+			// Do some work
+			if(m_quit)
+			{
+				break;
+			}
+
+			task = m_head;
+
+			// Update the queue
+			if(m_head->m_next == nullptr)
+			{
+				ANKI_ASSERT(m_tail == m_head);
+				m_head = m_tail = nullptr;
+			}
+			else
+			{
+				m_head = m_head->m_next;
+			}
+		}
+
+		// Exec the task
+		(*task)();
+
+		// Delete the task
+		m_alloc.deleteInstance(task);
+	}
+}
+
+} // end namespace anki
+

+ 6 - 0
src/resource/ResourceManager.cpp

@@ -4,6 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include "anki/resource/ResourceManager.h"
 #include "anki/resource/ResourceManager.h"
+#include "anki/resource/AsyncLoader.h"
 #include "anki/resource/Animation.h"
 #include "anki/resource/Animation.h"
 #include "anki/resource/Material.h"
 #include "anki/resource/Material.h"
 #include "anki/resource/Mesh.h"
 #include "anki/resource/Mesh.h"
@@ -68,11 +69,16 @@ ResourceManager::ResourceManager(Initializer& init)
 	ANKI_RESOURCE(DummyRsrc)
 	ANKI_RESOURCE(DummyRsrc)
 
 
 #undef ANKI_RESOURCE
 #undef ANKI_RESOURCE
+
+	// Init the thread
+	m_asyncLoader = m_alloc.newInstance<AsyncLoader>(m_alloc);
 }
 }
 
 
 //==============================================================================
 //==============================================================================
 ResourceManager::~ResourceManager()
 ResourceManager::~ResourceManager()
 {
 {
+	m_alloc.deleteInstance(m_asyncLoader);
+
 	ANKI_ASSERT(m_tmpAlloc.getMemoryPool().getAllocationsCount() == 0
 	ANKI_ASSERT(m_tmpAlloc.getMemoryPool().getAllocationsCount() == 0
 		&& "Forgot to deallocate");
 		&& "Forgot to deallocate");
 }
 }

+ 1 - 1
src/util/Thread.cpp

@@ -77,7 +77,7 @@ public:
 	}
 	}
 
 
 private:
 private:
-	/// Thread loop
+	/// Thread callaback
 	static I threadCallback(Thread::Info& info)
 	static I threadCallback(Thread::Info& info)
 	{
 	{
 		ThreadpoolThread& self = 
 		ThreadpoolThread& self = 

+ 4 - 4
testapp/Main.cpp

@@ -308,7 +308,7 @@ void execStdinScpripts()
 I32 mainLoopExtra(App& app, void*)
 I32 mainLoopExtra(App& app, void*)
 {
 {
 	F32 dist = 0.1;
 	F32 dist = 0.1;
-	F32 ang = toRad(1.5);
+	F32 ang = toRad(2.5);
 	F32 scale = 0.01;
 	F32 scale = 0.01;
 	F32 mouseSensivity = 9.0;
 	F32 mouseSensivity = 9.0;
 
 
@@ -481,13 +481,13 @@ void initSubsystems(int argc, char* argv[])
 	config.set("is.sm.resolution", 1024);
 	config.set("is.sm.resolution", 1024);
 	config.set("pps.enabled", true);
 	config.set("pps.enabled", true);
 	config.set("pps.hdr.enabled", true);
 	config.set("pps.hdr.enabled", true);
-	config.set("pps.hdr.renderingQuality", 0.6);
+	config.set("pps.hdr.renderingQuality", 0.5);
 	config.set("pps.hdr.blurringDist", 1.0);
 	config.set("pps.hdr.blurringDist", 1.0);
 	config.set("pps.hdr.blurringIterationsCount", 2);
 	config.set("pps.hdr.blurringIterationsCount", 2);
-	config.set("pps.hdr.exposure", 8.0);
+	config.set("pps.hdr.exposure", 10.0);
 	config.set("pps.hdr.samples", 17);
 	config.set("pps.hdr.samples", 17);
 	config.set("pps.sslr.enabled", true);
 	config.set("pps.sslr.enabled", true);
-	config.set("pps.sslr.renderingQuality", 0.35);
+	config.set("pps.sslr.renderingQuality", 0.5);
 	config.set("pps.sslr.blurringIterationsCount", 1);
 	config.set("pps.sslr.blurringIterationsCount", 1);
 	config.set("pps.ssao.blurringIterationsCount", 2);
 	config.set("pps.ssao.blurringIterationsCount", 2);
 	config.set("pps.ssao.enabled", true);
 	config.set("pps.ssao.enabled", true);

+ 188 - 0
tests/resource/AsyncLoader.cpp

@@ -0,0 +1,188 @@
+// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "tests/framework/Framework.h"
+#include "anki/resource/AsyncLoader.h"
+#include "anki/util/HighRezTimer.h"
+#include "anki/util/Atomic.h"
+#include "anki/util/Functions.h"
+
+namespace anki {
+
+//==============================================================================
+class Task: public AsyncLoader::Task
+{
+public:
+	F32 m_sleepTime = 0.0;
+	Barrier* m_barrier = nullptr;
+	AtomicU32* m_count = nullptr;
+	I32 m_id = -1;
+
+	Task(F32 time, Barrier* barrier, AtomicU32* count, I32 id = -1)
+	:	m_sleepTime(time),
+		m_barrier(barrier),
+		m_count(count),
+		m_id(id)
+	{}
+
+	void operator()()
+	{
+		if(m_count)
+		{
+			auto x = m_count->fetch_add(1);
+
+			if(m_id >= 0)
+			{
+				if(m_id != static_cast<I32>(x))
+				{
+					throw ANKI_EXCEPTION("Wrong excecution order");
+				}
+			}
+		}
+
+		if(m_sleepTime != 0.0)
+		{
+			HighRezTimer::sleep(m_sleepTime);
+		}
+
+		if(m_barrier)
+		{
+			m_barrier->wait();
+		}
+	}
+};
+
+//==============================================================================
+class MemTask: public AsyncLoader::Task
+{
+public:
+	HeapAllocator<U8> m_alloc;
+	Barrier* m_barrier = nullptr;
+
+	MemTask(HeapAllocator<U8> alloc, Barrier* barrier)
+	:	m_alloc(alloc),
+		m_barrier(barrier)
+	{}
+
+	void operator()()
+	{
+		void* mem = m_alloc.allocate(10);
+		HighRezTimer::sleep(0.1);
+
+		m_alloc.deallocate(mem, 10);
+
+		if(m_barrier)
+		{
+			m_barrier->wait();
+		}
+	}
+};
+
+//==============================================================================
+ANKI_TEST(Resource, AsyncLoader)
+{
+	HeapAllocator<U8> alloc(HeapMemoryPool(allocAligned, nullptr));
+
+	// Simple create destroy
+	{
+		AsyncLoader a(alloc);
+	}
+
+	// Simple task that will finish
+	{
+		AsyncLoader a(alloc);
+		Barrier barrier(2);
+
+		a.newTask<Task>(0.0, &barrier, nullptr);
+		barrier.wait();
+	}
+
+	// Many tasks that will finish
+	{
+		AsyncLoader a(alloc);
+		Barrier barrier(2);
+		AtomicU32 counter = {0};
+
+		for(U i = 0; i < 100; i++)
+		{
+			Barrier* pbarrier = nullptr;
+
+			if(i == 99)
+			{
+				pbarrier = &barrier;
+			}
+
+			a.newTask<Task>(0.0, pbarrier, &counter);
+		}
+
+		barrier.wait();
+
+		ANKI_TEST_EXPECT_EQ(counter.load(), 100);
+	}
+
+	// Many tasks that will _not_ finish
+	{
+		AsyncLoader a(alloc);
+
+		for(U i = 0; i < 100; i++)
+		{
+			a.newTask<Task>(0.0, nullptr, nullptr);
+		}
+	}
+
+	// Tasks that allocate
+	{
+		AsyncLoader a(alloc);
+		Barrier barrier(2);
+
+		for(U i = 0; i < 10; i++)
+		{
+			Barrier* pbarrier = nullptr;
+
+			if(i == 9)
+			{
+				pbarrier = &barrier;
+			}
+
+			a.newTask<MemTask>(alloc, pbarrier);
+		}
+
+		barrier.wait();
+	}
+
+	// Tasks that allocate and never finished
+	{
+		AsyncLoader a(alloc);
+
+		for(U i = 0; i < 10; i++)
+		{
+			a.newTask<MemTask>(alloc, nullptr);
+		}
+	}
+
+	// Random struf
+	{
+		AsyncLoader a(alloc);
+		Barrier barrier(2);
+		AtomicU32 counter = {0};
+
+		for(U i = 0; i < 10; i++)
+		{
+			Barrier* pbarrier = nullptr;
+
+			if(i == 9)
+			{
+				pbarrier = &barrier;
+			}
+
+			a.newTask<Task>(randRange(0.0, 0.5), pbarrier, &counter, i);
+		}
+
+		barrier.wait();
+		ANKI_TEST_EXPECT_EQ(counter.load(), 10);
+	}
+}
+
+} // end namespace anki