| @@ -61,7 +61,7 @@ static int const TEXTURE_WIDTH = 256; | |||||
| #define HST_CLAMP 1.f | #define HST_CLAMP 1.f | ||||
| #define WITH_TEXTURE 0 | #define WITH_TEXTURE 0 | ||||
| #define WITH_THREAD_MANAGER 0 // FIXME: this was removed from Lol Engine | |||||
| #define HAS_KBOARD (m_input_usage & (1<<IPT_MV_KBOARD)) | #define HAS_KBOARD (m_input_usage & (1<<IPT_MV_KBOARD)) | ||||
| #define HAS_MOUSE (m_input_usage & (1<<IPT_MV_MOUSE)) | #define HAS_MOUSE (m_input_usage & (1<<IPT_MV_MOUSE)) | ||||
| @@ -163,7 +163,9 @@ void MeshViewer::Start() | |||||
| //Threads setup | //Threads setup | ||||
| m_entities << (m_file_check = new FileUpdateTester()); | m_entities << (m_file_check = new FileUpdateTester()); | ||||
| #if WITH_THREAD_MANAGER | |||||
| m_entities << (m_file_loader = new DefaultThreadManager(4, 0)); | m_entities << (m_file_loader = new DefaultThreadManager(4, 0)); | ||||
| #endif | |||||
| //Scene setup | //Scene setup | ||||
| m_ssetup_file_name = "../data/meshviewer.init.lua"; | m_ssetup_file_name = "../data/meshviewer.init.lua"; | ||||
| @@ -171,7 +173,9 @@ void MeshViewer::Start() | |||||
| //Mesh file | //Mesh file | ||||
| m_file_status = m_file_check->RegisterFile(m_file_name); | m_file_status = m_file_check->RegisterFile(m_file_name); | ||||
| #if WITH_THREAD_MANAGER | |||||
| m_file_loader->AddJob(GetLoadJob(m_file_name)); | m_file_loader->AddJob(GetLoadJob(m_file_name)); | ||||
| #endif | |||||
| //Camera setup | //Camera setup | ||||
| m_camera = new Camera(); | m_camera = new Camera(); | ||||
| @@ -233,7 +237,9 @@ void MeshViewer::Stop() | |||||
| m_camera = nullptr; | m_camera = nullptr; | ||||
| m_controller = nullptr; | m_controller = nullptr; | ||||
| m_file_check = nullptr; | m_file_check = nullptr; | ||||
| #if WITH_THREAD_MANAGER | |||||
| m_file_loader = nullptr; | m_file_loader = nullptr; | ||||
| #endif | |||||
| /** ----- Init is needed ----- **/ | /** ----- Init is needed ----- **/ | ||||
| m_init = false; | m_init = false; | ||||
| @@ -345,15 +351,19 @@ void MeshViewer::tick_game(float seconds) | |||||
| //Check file update | //Check file update | ||||
| ASSERT(m_file_status); | ASSERT(m_file_status); | ||||
| //if (false) //DEBUG | //if (false) //DEBUG | ||||
| #if WITH_THREAD_MANAGER | |||||
| //m_file_status->GetTime() | //m_file_status->GetTime() | ||||
| if (m_file_status->HasUpdated()) | if (m_file_status->HasUpdated()) | ||||
| m_file_loader->AddJob(GetLoadJob(m_file_name)); | m_file_loader->AddJob(GetLoadJob(m_file_name)); | ||||
| #endif | |||||
| //Check work done | //Check work done | ||||
| //if (false) //DEBUG | //if (false) //DEBUG | ||||
| { | { | ||||
| array<ThreadJob*> result; | array<ThreadJob*> result; | ||||
| #if WITH_THREAD_MANAGER | |||||
| m_file_loader->GetWorkResult(result); | m_file_loader->GetWorkResult(result); | ||||
| #endif | |||||
| if (result.count()) | if (result.count()) | ||||
| { | { | ||||
| for (ThreadJob* job : result) | for (ThreadJob* job : result) | ||||
| @@ -114,8 +114,7 @@ liblol_core_sources = \ | |||||
| mesh/mesh.cpp mesh/mesh.h \ | mesh/mesh.cpp mesh/mesh.h \ | ||||
| mesh/primitivemesh.cpp mesh/primitivemesh.h \ | mesh/primitivemesh.cpp mesh/primitivemesh.h \ | ||||
| \ | \ | ||||
| sys/init.cpp sys/file.cpp sys/hacks.cpp \ | |||||
| sys/thread.cpp sys/threadtypes.cpp sys/getopt.cpp \ | |||||
| sys/init.cpp sys/file.cpp sys/hacks.cpp sys/getopt.cpp \ | |||||
| \ | \ | ||||
| image/resource.cpp image/resource-private.h \ | image/resource.cpp image/resource-private.h \ | ||||
| image/image.cpp image/image-private.h image/kernel.cpp image/pixel.cpp \ | image/image.cpp image/image-private.h image/kernel.cpp image/pixel.cpp \ | ||||
| @@ -177,8 +177,6 @@ | |||||
| <ClCompile Include="sys\getopt.cpp" /> | <ClCompile Include="sys\getopt.cpp" /> | ||||
| <ClCompile Include="sys\hacks.cpp" /> | <ClCompile Include="sys\hacks.cpp" /> | ||||
| <ClCompile Include="sys\init.cpp" /> | <ClCompile Include="sys\init.cpp" /> | ||||
| <ClCompile Include="sys\thread.cpp" /> | |||||
| <ClCompile Include="sys\threadtypes.cpp" /> | |||||
| <ClCompile Include="text.cpp" /> | <ClCompile Include="text.cpp" /> | ||||
| <ClCompile Include="textureimage.cpp" /> | <ClCompile Include="textureimage.cpp" /> | ||||
| <ClCompile Include="tileset.cpp" /> | <ClCompile Include="tileset.cpp" /> | ||||
| @@ -277,7 +275,6 @@ | |||||
| <ClInclude Include="lol\sys\getopt.h" /> | <ClInclude Include="lol\sys\getopt.h" /> | ||||
| <ClInclude Include="lol\sys\init.h" /> | <ClInclude Include="lol\sys\init.h" /> | ||||
| <ClInclude Include="lol\sys\thread.h" /> | <ClInclude Include="lol\sys\thread.h" /> | ||||
| <ClInclude Include="lol\sys\threadtypes.h" /> | |||||
| <ClInclude Include="lol\sys\timer.h" /> | <ClInclude Include="lol\sys\timer.h" /> | ||||
| <ClInclude Include="mesh\mesh.h" /> | <ClInclude Include="mesh\mesh.h" /> | ||||
| <ClInclude Include="mesh\primitivemesh.h" /> | <ClInclude Include="mesh\primitivemesh.h" /> | ||||
| @@ -303,9 +303,6 @@ | |||||
| <ClCompile Include="image\codec\zed-palette-image.cpp"> | <ClCompile Include="image\codec\zed-palette-image.cpp"> | ||||
| <Filter>image\codec</Filter> | <Filter>image\codec</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||
| <ClCompile Include="sys\thread.cpp"> | |||||
| <Filter>sys</Filter> | |||||
| </ClCompile> | |||||
| <ClCompile Include="image\filter\colors.cpp"> | <ClCompile Include="image\filter\colors.cpp"> | ||||
| <Filter>image\filter</Filter> | <Filter>image\filter</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||
| @@ -366,9 +363,6 @@ | |||||
| <ClCompile Include="lolua\baselua.cpp"> | <ClCompile Include="lolua\baselua.cpp"> | ||||
| <Filter>lolua</Filter> | <Filter>lolua</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||
| <ClCompile Include="sys\threadtypes.cpp"> | |||||
| <Filter>sys</Filter> | |||||
| </ClCompile> | |||||
| <ClCompile Include="tileset.cpp"> | <ClCompile Include="tileset.cpp"> | ||||
| <Filter>tileset</Filter> | <Filter>tileset</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||
| @@ -712,9 +706,6 @@ | |||||
| <ClInclude Include="lolua\baselua.h"> | <ClInclude Include="lolua\baselua.h"> | ||||
| <Filter>lolua</Filter> | <Filter>lolua</Filter> | ||||
| </ClInclude> | </ClInclude> | ||||
| <ClInclude Include="lol\sys\threadtypes.h"> | |||||
| <Filter>lol\sys</Filter> | |||||
| </ClInclude> | |||||
| <ClInclude Include="tileset.h"> | <ClInclude Include="tileset.h"> | ||||
| <Filter>tileset</Filter> | <Filter>tileset</Filter> | ||||
| </ClInclude> | </ClInclude> | ||||
| @@ -1,7 +1,7 @@ | |||||
| // | // | ||||
| // Lol Engine | // Lol Engine | ||||
| // | // | ||||
| // Copyright © 2010—2016 Sam Hocevar <sam@hocevar.net> | |||||
| // Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net> | |||||
| // | // | ||||
| // Lol Engine is free software. It comes without any warranty, to | // Lol Engine is free software. It comes without any warranty, to | ||||
| // the extent permitted by applicable law. You can redistribute it | // the extent permitted by applicable law. You can redistribute it | ||||
| @@ -14,7 +14,6 @@ | |||||
| #include <lol/sys/thread.h> | #include <lol/sys/thread.h> | ||||
| #include <lol/sys/timer.h> /* requires thread.h */ | #include <lol/sys/timer.h> /* requires thread.h */ | ||||
| #include <lol/sys/threadtypes.h> | |||||
| #include <lol/sys/getopt.h> | #include <lol/sys/getopt.h> | ||||
| #include <lol/sys/init.h> | #include <lol/sys/init.h> | ||||
| #include <lol/sys/file.h> | #include <lol/sys/file.h> | ||||
| @@ -1,278 +0,0 @@ | |||||
| // | |||||
| // Lol Engine | |||||
| // | |||||
| // Copyright © 2010—2018 Sam Hocevar <sam@hocevar.net> | |||||
| // © 2013—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> | |||||
| // | |||||
| // Lol Engine is free software. It comes without any warranty, to | |||||
| // the extent permitted by applicable law. You can redistribute it | |||||
| // and/or modify it under the terms of the Do What the Fuck You Want | |||||
| // to Public License, Version 2, as published by the WTFPL Task Force. | |||||
| // See http://www.wtfpl.net/ for more details. | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // The Threading classes | |||||
| // --------------------- | |||||
| // | |||||
| #include "engine/entity.h" | |||||
| #include <map> | |||||
| namespace lol | |||||
| { | |||||
| //ThreadStatus ---------------------------------------------------------------- | |||||
| struct ThreadStatusBase : public StructSafeEnum | |||||
| { | |||||
| enum Type | |||||
| { | |||||
| NOTHING, | |||||
| THREAD_STARTED, | |||||
| THREAD_WORKING, | |||||
| THREAD_IDLE, | |||||
| THREAD_STOPPED, | |||||
| }; | |||||
| protected: | |||||
| virtual bool BuildEnumMap(std::map<int64_t, std::string>& enum_map) | |||||
| { | |||||
| enum_map[NOTHING] = "NOTHING"; | |||||
| enum_map[THREAD_STARTED] = "THREAD_STARTED"; | |||||
| enum_map[THREAD_STOPPED] = "THREAD_STOPPED"; | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| typedef SafeEnum<ThreadStatusBase> ThreadStatus; | |||||
| struct ThreadJobTypeBase : public StructSafeEnum | |||||
| { | |||||
| enum Type | |||||
| { | |||||
| NONE, | |||||
| WORK_TODO, | |||||
| WORK_SUCCEEDED, | |||||
| WORK_FAILED, | |||||
| WORK_DONE, | |||||
| THREAD_STOP | |||||
| }; | |||||
| protected: | |||||
| virtual bool BuildEnumMap(std::map<int64_t, std::string>& enum_map) | |||||
| { | |||||
| enum_map[NONE] = "NONE"; | |||||
| enum_map[WORK_TODO] = "WORK_TODO"; | |||||
| enum_map[WORK_SUCCEEDED] = "WORK_SUCCEEDED"; | |||||
| enum_map[WORK_FAILED] = "WORK_FAILED"; | |||||
| enum_map[WORK_DONE] = "WORK_DONE"; | |||||
| enum_map[THREAD_STOP] = "THREAD_STOP"; | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| typedef SafeEnum<ThreadJobTypeBase> ThreadJobType; | |||||
| //ThreadJob ------------------------------------------------------------------- | |||||
| class ThreadJob | |||||
| { | |||||
| friend class BaseThreadManager; | |||||
| protected: | |||||
| inline ThreadJob(ThreadJobType type) : m_type(type) {} | |||||
| public: | |||||
| std::string GetName() const { return "<ThreadJob>"; } | |||||
| inline ThreadJob() : m_type(ThreadJobType::NONE) {} | |||||
| virtual ~ThreadJob() {} | |||||
| ThreadJobType GetJobType() { return m_type; } | |||||
| void SetJobType(ThreadJobType type) { m_type = type; } | |||||
| bool operator==(const ThreadJobType& o) { return GetJobType() == o; } | |||||
| protected: | |||||
| virtual bool DoWork() { return false; } | |||||
| ThreadJobType m_type; | |||||
| }; | |||||
| //Base class for thread manager ----------------------------------------------- | |||||
| class BaseThreadManager : public Entity | |||||
| { | |||||
| public: | |||||
| std::string GetName() const { return "<BaseThreadManager>"; } | |||||
| BaseThreadManager(int thread_max); | |||||
| BaseThreadManager(int thread_max, int thread_min); | |||||
| virtual ~BaseThreadManager(); | |||||
| //Base setup | |||||
| void Setup(int thread_max); | |||||
| void Setup(int thread_max, int thread_min); | |||||
| //Initialize, Ticker::Ref and start the thread | |||||
| bool Start(); | |||||
| //Stop the threads | |||||
| bool Stop(); | |||||
| private: | |||||
| //Work stuff | |||||
| bool AddThreadWork(ThreadJob* job); | |||||
| protected: | |||||
| //Thread count management | |||||
| void AdjustThreadCount(int count); | |||||
| void CleanAddedThread(bool wait = false); | |||||
| void CleanRemovedThread(bool wait = false); | |||||
| int GetDispatchCount(); | |||||
| int GetDispatchedCount(); | |||||
| //Dispatch job to threads | |||||
| void DispatchJob(ThreadJob* job); | |||||
| void DispatchJob(array<ThreadJob*> const& jobs); | |||||
| //Fetch Results | |||||
| bool FetchResult(array<ThreadJob*>& results); | |||||
| //Base thread work function | |||||
| void BaseThreadWork(thread* inst); | |||||
| virtual void tick_game(float seconds); | |||||
| //Default behaviour : delete the job result | |||||
| virtual void TreatResult(ThreadJob* result) { delete(result); } | |||||
| private: | |||||
| /* Jobs */ | |||||
| array<ThreadJob*> m_job_dispatch; | |||||
| int m_job_dispatched = 0; | |||||
| #if LOL_FEATURE_THREADS | |||||
| /* Worker threads */ | |||||
| int m_thread_max = 0; | |||||
| int m_thread_min = 0; | |||||
| int m_thread_active = 0; | |||||
| array<thread*> m_threads; | |||||
| timer m_thread_added_timer; | |||||
| int m_thread_added = 0; | |||||
| timer m_thread_removed_timer; | |||||
| int m_thread_removed = 0; | |||||
| queue<ThreadStatus> m_statusqueue; | |||||
| queue<thread*> m_spawnqueue, m_donequeue; | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| queue<ThreadJob*> m_jobqueue; | |||||
| queue<ThreadJob*> m_resultqueue; | |||||
| }; | |||||
| //Generic class for thread manager, executes work and store results, with no specific treatment | |||||
| class DefaultThreadManager : public BaseThreadManager | |||||
| { | |||||
| public: | |||||
| std::string GetName() const { return "<DefaultThreadManager>"; } | |||||
| DefaultThreadManager(int thread_max) | |||||
| : BaseThreadManager(thread_max, thread_max) | |||||
| { } | |||||
| DefaultThreadManager(int thread_max, int thread_min) | |||||
| : BaseThreadManager(thread_max, thread_min) | |||||
| { } | |||||
| //Work stuff | |||||
| void AddJob(ThreadJob* job); | |||||
| bool GetWorkResult(array<ThreadJob*>& results); | |||||
| protected: | |||||
| virtual void TreatResult(ThreadJob* result) { m_job_result << result; } | |||||
| array<ThreadJob*> m_job_result; | |||||
| }; | |||||
| //FileUpdateTester ------------------------------------------------------------ | |||||
| //Test the files registered and warns when they update | |||||
| class FileUpdateTester : public BaseThreadManager | |||||
| { | |||||
| typedef BaseThreadManager super; | |||||
| public: | |||||
| class Status | |||||
| { | |||||
| friend class FileUpdateTester; | |||||
| protected: | |||||
| mutex m_mutex; | |||||
| long int m_time = 0; | |||||
| bool m_updated = false; | |||||
| public: | |||||
| void SetTime(long int time) | |||||
| { | |||||
| m_mutex.lock(); | |||||
| m_time = time; | |||||
| m_mutex.unlock(); | |||||
| } | |||||
| long int GetTime() | |||||
| { | |||||
| m_mutex.lock(); | |||||
| long int time = m_time; | |||||
| m_mutex.unlock(); | |||||
| return time; | |||||
| } | |||||
| void SetUpdated(bool updated) | |||||
| { | |||||
| m_mutex.lock(); | |||||
| m_updated = updated; | |||||
| m_mutex.unlock(); | |||||
| } | |||||
| bool HasUpdated() | |||||
| { | |||||
| m_mutex.lock(); | |||||
| bool updated = m_updated; | |||||
| m_mutex.unlock(); | |||||
| return updated; | |||||
| } | |||||
| }; | |||||
| public: | |||||
| std::string GetName() const { return "<FileUpdateTester>"; } | |||||
| FileUpdateTester(uint32_t frame_skip = 4) | |||||
| : BaseThreadManager(1) | |||||
| { m_frame_skip = frame_skip; } | |||||
| virtual ~FileUpdateTester(); | |||||
| //------------------------------------------------------------------------- | |||||
| FileUpdateTester::Status* RegisterFile(std::string const& path); | |||||
| protected: | |||||
| void UnregisterFile(std::string const& path); | |||||
| public: | |||||
| void UnregisterFile(FileUpdateTester::Status*& status); | |||||
| //TODO: Add directories | |||||
| //FileUpdateTester::Status* RegisterDirectory(std::string const& path, bool check_files=false); | |||||
| virtual void tick_game(float seconds); | |||||
| virtual void TreatResult(ThreadJob* result); | |||||
| private: | |||||
| uint32_t m_frame_skip = 4; | |||||
| uint32_t m_frame_count = 0; | |||||
| array<ThreadJob*> m_job_done; | |||||
| std::map<std::string, Status*> m_files; | |||||
| }; | |||||
| typedef FileUpdateTester::Status FileUpdateStatus; | |||||
| //AsyncImageLoader ------------------------------------------------------------ | |||||
| //Load images asynchronously, automatically updating the dummy image | |||||
| class AsyncImageLoader : public BaseThreadManager | |||||
| { | |||||
| public: | |||||
| std::string GetName() const { return "<AsyncImageLoader>"; } | |||||
| AsyncImageLoader(int thread_max) | |||||
| : BaseThreadManager(thread_max, 0) | |||||
| { | |||||
| m_dummy_image.DummyFill(); | |||||
| } | |||||
| virtual ~AsyncImageLoader() | |||||
| { } | |||||
| //Returns a dummy image, and start a job to load the image on a thread | |||||
| image* Load(const std::string& path); | |||||
| bool CheckStatus(image* img); | |||||
| protected: | |||||
| virtual void TreatResult(ThreadJob* result); | |||||
| private: | |||||
| image m_dummy_image; | |||||
| std::map<std::string, image*> m_images; | |||||
| array<image*> m_loaded_images; | |||||
| }; | |||||
| } /* namespace lol */ | |||||
| @@ -1,286 +0,0 @@ | |||||
| // | |||||
| // Lol Engine | |||||
| // | |||||
| // Copyright © 2010—2015 Sam Hocevar <sam@hocevar.net> | |||||
| // © 2014—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> | |||||
| // | |||||
| // Lol Engine is free software. It comes without any warranty, to | |||||
| // the extent permitted by applicable law. You can redistribute it | |||||
| // and/or modify it under the terms of the Do What the Fuck You Want | |||||
| // to Public License, Version 2, as published by the WTFPL Task Force. | |||||
| // See http://www.wtfpl.net/ for more details. | |||||
| // | |||||
| #include <lol/engine-internal.h> | |||||
| namespace lol | |||||
| { | |||||
| //BaseThreadManager ----------------------------------------------------------- | |||||
| BaseThreadManager::BaseThreadManager(int thread_max) : BaseThreadManager(thread_max, thread_max) | |||||
| { } | |||||
| BaseThreadManager::BaseThreadManager(int thread_max, int thread_min) | |||||
| { | |||||
| Setup(thread_max, thread_min); | |||||
| } | |||||
| BaseThreadManager::~BaseThreadManager() | |||||
| { | |||||
| Stop(); | |||||
| } | |||||
| //Base Setup ------------------------------------------------------------------ | |||||
| void BaseThreadManager::Setup(int thread_max) | |||||
| { | |||||
| Setup(thread_max, thread_max); | |||||
| } | |||||
| void BaseThreadManager::Setup(int thread_max, int thread_min) | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| m_thread_max = thread_max; | |||||
| m_thread_min = thread_min; | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| //Initialize, Ticker::Ref and start the thread -------------------------------- | |||||
| bool BaseThreadManager::Start() | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| ASSERT(!!m_thread_max, "Thread count shouldn't be zero"); | |||||
| if (m_threads.count() > 0) | |||||
| return false; | |||||
| //Add minimum threads | |||||
| m_threads.reserve(m_thread_max); | |||||
| //AddThreads(m_thread_min); | |||||
| AdjustThreadCount(m_thread_min); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| return true; | |||||
| } | |||||
| //Stop the threads ------------------------------------------------------------ | |||||
| bool BaseThreadManager::Stop() | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| if (m_threads.count() <= 0) | |||||
| return false; | |||||
| //End all threads | |||||
| //RemoveThreads((int)m_threads.count()); | |||||
| AdjustThreadCount(0); | |||||
| CleanAddedThread(true); | |||||
| CleanRemovedThread(true); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| return true; | |||||
| } | |||||
| //Work stuff ------------------------------------------------------------------ | |||||
| bool BaseThreadManager::AddThreadWork(ThreadJob* job) | |||||
| { | |||||
| return m_jobqueue.try_push(job); | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void BaseThreadManager::AdjustThreadCount(int count) | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| int actual_count = (int)m_threads.count() - m_thread_removed; | |||||
| int diff = count - actual_count; | |||||
| for (int i = 0; i < lol::abs(diff); i++) | |||||
| { | |||||
| if (diff > 0) | |||||
| { | |||||
| //Spawn worker threads and ... | |||||
| m_threads << new thread(std::bind(&BaseThreadManager::BaseThreadWork, this, std::placeholders::_1)); | |||||
| m_thread_added++; | |||||
| m_thread_added_timer.reset(); | |||||
| } | |||||
| else | |||||
| { | |||||
| //Signal worker threads for completion and ... | |||||
| m_jobqueue.push(new ThreadJob(ThreadJobType::THREAD_STOP)); | |||||
| m_thread_removed++; | |||||
| m_thread_removed_timer.reset(); | |||||
| } | |||||
| } | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void BaseThreadManager::CleanAddedThread(bool wait) | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| //... Wait for their readiness. | |||||
| while (m_thread_added > 0) | |||||
| { | |||||
| thread* inst = nullptr; | |||||
| bool found = false; | |||||
| if (wait) | |||||
| found = !!(inst = m_spawnqueue.pop()); | |||||
| else | |||||
| found = m_spawnqueue.try_pop(inst); | |||||
| if (found) | |||||
| m_thread_added--; | |||||
| else | |||||
| break; | |||||
| } | |||||
| //Assert if spawning took too much time | |||||
| //ASSERT(!(m_thread_added > 0 && m_thread_added_timer.poll() > 5.f)); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void BaseThreadManager::CleanRemovedThread(bool wait) | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| //... Wait for them to quit. | |||||
| while (m_thread_removed > 0) | |||||
| { | |||||
| thread* inst = nullptr; | |||||
| bool found = false; | |||||
| if (wait) | |||||
| found = !!(inst = m_donequeue.pop()); | |||||
| else | |||||
| found = m_donequeue.try_pop(inst); | |||||
| if (found) | |||||
| { | |||||
| m_thread_removed--; | |||||
| m_threads.remove_swap_item(inst); | |||||
| delete inst; | |||||
| } | |||||
| else | |||||
| break; | |||||
| } | |||||
| //Assert if stopping took too much time | |||||
| //ASSERT(!(m_thread_removed > 0 && m_thread_removed_timer.poll() > 5.f)); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| int BaseThreadManager::GetDispatchCount() | |||||
| { | |||||
| return (int)m_job_dispatch.count(); | |||||
| } | |||||
| int BaseThreadManager::GetDispatchedCount() | |||||
| { | |||||
| return m_job_dispatched; | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void BaseThreadManager::DispatchJob(ThreadJob* job) | |||||
| { | |||||
| if (job) m_job_dispatch << job; | |||||
| } | |||||
| void BaseThreadManager::DispatchJob(array<ThreadJob*> const& jobs) | |||||
| { | |||||
| m_job_dispatch += jobs; | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| bool BaseThreadManager::FetchResult(array<ThreadJob*>& results) | |||||
| { | |||||
| ThreadJob* result; | |||||
| while (m_resultqueue.try_pop(result)) | |||||
| results << result; | |||||
| return results.count() > 0; | |||||
| } | |||||
| //Base thread work function --------------------------------------------------- | |||||
| void BaseThreadManager::BaseThreadWork(thread* inst) | |||||
| { | |||||
| ThreadJob* job = nullptr; | |||||
| #if LOL_FEATURE_THREADS | |||||
| //Register that the thread has started | |||||
| m_statusqueue.push(ThreadStatus::THREAD_STARTED); | |||||
| m_spawnqueue.push(inst); | |||||
| for (;;) | |||||
| #else //LOL_FEATURE_THREADS | |||||
| while (m_jobqueue.try_pop(job)) | |||||
| #endif | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| //Retrieve a job | |||||
| job = m_jobqueue.pop(); | |||||
| //Stop thread | |||||
| if (job->GetJobType() == ThreadJobType::THREAD_STOP) | |||||
| { | |||||
| delete job; | |||||
| break; | |||||
| } | |||||
| //Or work | |||||
| else | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| if (*job == ThreadJobType::WORK_TODO) | |||||
| { | |||||
| #if LOL_FEATURE_THREADS | |||||
| m_statusqueue.push(ThreadStatus::THREAD_WORKING); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| if (job->DoWork()) | |||||
| job->SetJobType(ThreadJobType::WORK_SUCCEEDED); | |||||
| else | |||||
| job->SetJobType(ThreadJobType::WORK_FAILED); | |||||
| m_resultqueue.push(job); | |||||
| #if LOL_FEATURE_THREADS | |||||
| m_statusqueue.push(ThreadStatus::THREAD_IDLE); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| } | |||||
| #if LOL_FEATURE_THREADS | |||||
| //Register that the thread has stopped | |||||
| m_statusqueue.push(ThreadStatus::THREAD_STOPPED); | |||||
| m_donequeue.push(inst); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void BaseThreadManager::tick_game(float seconds) | |||||
| { | |||||
| Entity::tick_game(seconds); | |||||
| //Start if needed | |||||
| Start(); | |||||
| //Dispatch work task | |||||
| while (m_job_dispatch.count() > 0 && AddThreadWork(m_job_dispatch[0])) | |||||
| { | |||||
| m_job_dispatch.remove(0); | |||||
| //Keep track of added jobs | |||||
| m_job_dispatched++; | |||||
| } | |||||
| //Execute one task per frame if thread are not available | |||||
| #if !defined(LOL_FEATURE_THREADS) || !LOL_FEATURE_THREADS | |||||
| BaseThreadWork(nullptr); | |||||
| #endif // !LOL_FEATURE_THREADS | |||||
| array<ThreadJob*> result; | |||||
| //Fetch and treat results | |||||
| if (FetchResult(result)) | |||||
| { | |||||
| for (int i = 0; i < result.count(); i++) | |||||
| { | |||||
| ThreadJob* job = result[i]; | |||||
| TreatResult(job); | |||||
| //Remove job from count as it has been treated | |||||
| m_job_dispatched--; | |||||
| } | |||||
| } | |||||
| //Resize thread count if needed | |||||
| #if LOL_FEATURE_THREADS | |||||
| ThreadStatus status; | |||||
| while (m_statusqueue.try_pop(status)) | |||||
| m_thread_active += status == ThreadStatus::THREAD_IDLE ? -1 : +1; | |||||
| AdjustThreadCount(lol::clamp(m_job_dispatched, m_thread_min, m_thread_max)); | |||||
| CleanAddedThread(); | |||||
| CleanRemovedThread(); | |||||
| #endif //LOL_FEATURE_THREADS | |||||
| } | |||||
| } /* namespace lol */ | |||||
| @@ -1,235 +0,0 @@ | |||||
| // | |||||
| // Lol Engine | |||||
| // | |||||
| // Copyright © 2010—2018 Sam Hocevar <sam@hocevar.net> | |||||
| // © 2014—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> | |||||
| // | |||||
| // Lol Engine is free software. It comes without any warranty, to | |||||
| // the extent permitted by applicable law. You can redistribute it | |||||
| // and/or modify it under the terms of the Do What the Fuck You Want | |||||
| // to Public License, Version 2, as published by the WTFPL Task Force. | |||||
| // See http://www.wtfpl.net/ for more details. | |||||
| // | |||||
| #include <lol/engine-internal.h> | |||||
| using namespace lol; | |||||
| //DefaultThreadManager -------------------------------------------------------- | |||||
| void DefaultThreadManager::AddJob(ThreadJob* job) | |||||
| { | |||||
| DispatchJob(job); | |||||
| } | |||||
| bool DefaultThreadManager::GetWorkResult(array<ThreadJob*>& results) | |||||
| { | |||||
| results += m_job_result; | |||||
| m_job_result.clear(); | |||||
| return results.count() > 0; | |||||
| } | |||||
| //FileUpdateTesterJob --------------------------------------------------------- | |||||
| class FileUpdateTesterJob : public ThreadJob | |||||
| { | |||||
| friend class FileUpdateTester; | |||||
| public: | |||||
| std::string GetName() const { return "<FileUpdateTesterJob>"; } | |||||
| FileUpdateTesterJob() | |||||
| : ThreadJob(ThreadJobType::NONE) | |||||
| { | |||||
| } | |||||
| FileUpdateTesterJob(std::string const &path) | |||||
| : ThreadJob(ThreadJobType::WORK_TODO), | |||||
| m_path(path) | |||||
| { | |||||
| } | |||||
| std::string const &GetPath() { return m_path; } | |||||
| long int GetTime() { return m_time; } | |||||
| bool HasUpdated() { return m_updated; } | |||||
| void Restart() | |||||
| { | |||||
| SetJobType(ThreadJobType::WORK_TODO); | |||||
| m_updated = false; | |||||
| } | |||||
| protected: | |||||
| virtual bool DoWork() | |||||
| { | |||||
| array<std::string> pathlist = sys::get_path_list(m_path); | |||||
| File f; | |||||
| for (auto const &path : pathlist) | |||||
| { | |||||
| f.Open(path, FileAccess::Read); | |||||
| if (f.IsValid()) | |||||
| { | |||||
| long int new_time = f.GetModificationTime(); | |||||
| if (!m_ready) | |||||
| { | |||||
| m_time = new_time; | |||||
| m_ready = true; | |||||
| } | |||||
| else if (new_time > m_time) | |||||
| { | |||||
| m_time = new_time; | |||||
| m_updated = true; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| //----------------- | |||||
| bool m_ready = false; | |||||
| std::string m_path; | |||||
| long int m_time = 0; | |||||
| bool m_updated = false; | |||||
| }; | |||||
| //FileUpdateTester ------------------------------------------------------------ | |||||
| FileUpdateTester::~FileUpdateTester() | |||||
| { | |||||
| ASSERT(!m_files.size(), "Files need to be unregistered before destroying FileUpdateTester"); | |||||
| } | |||||
| //File interface -------------------------------------------------------------- | |||||
| FileUpdateTester::Status* FileUpdateTester::RegisterFile(std::string const& path) | |||||
| { | |||||
| DispatchJob(new FileUpdateTesterJob(path)); | |||||
| m_files[path] = new FileUpdateTester::Status(); | |||||
| return m_files[path]; | |||||
| } | |||||
| void FileUpdateTester::UnregisterFile(std::string const& path) | |||||
| { | |||||
| ASSERT(has_key(m_files, path)); | |||||
| delete m_files[path]; | |||||
| m_files.erase(path); | |||||
| } | |||||
| void FileUpdateTester::UnregisterFile(FileUpdateTester::Status*& status) | |||||
| { | |||||
| ASSERT(status); | |||||
| for (auto const &key : keys(m_files)) | |||||
| { | |||||
| if (m_files[key] == status) | |||||
| { | |||||
| UnregisterFile(key); | |||||
| status = nullptr; | |||||
| return; | |||||
| } | |||||
| } | |||||
| ASSERT(false, "Status wasn't found"); | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void FileUpdateTester::tick_game(float seconds) | |||||
| { | |||||
| // Reset update for this frame | |||||
| for (auto &kv : m_files) | |||||
| kv.second->SetUpdated(false); | |||||
| super::tick_game(seconds); | |||||
| if (!GetDispatchCount() && m_job_done.count()) | |||||
| { | |||||
| if (m_frame_count++ < m_frame_skip) | |||||
| return; | |||||
| m_frame_count = 0; | |||||
| DispatchJob(m_job_done); | |||||
| m_job_done.clear(); | |||||
| } | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void FileUpdateTester::TreatResult(ThreadJob* result) | |||||
| { | |||||
| FileUpdateTesterJob* job = static_cast<FileUpdateTesterJob*>(result); | |||||
| if (job->HasUpdated()) | |||||
| { | |||||
| m_files[job->GetPath()]->SetTime(job->GetTime()); | |||||
| m_files[job->GetPath()]->SetUpdated(true); | |||||
| } | |||||
| job->Restart(); | |||||
| m_job_done << job; | |||||
| } | |||||
| //AsyncImageJob --------------------------------------------------------------- | |||||
| class AsyncImageJob : public ThreadJob | |||||
| { | |||||
| public: | |||||
| std::string GetName() const { return "<AsyncImageJob>"; } | |||||
| AsyncImageJob() | |||||
| : ThreadJob(ThreadJobType::NONE) | |||||
| { | |||||
| } | |||||
| AsyncImageJob(std::string const &path) | |||||
| : ThreadJob(ThreadJobType::WORK_TODO), | |||||
| m_path(path) | |||||
| { | |||||
| } | |||||
| std::string const& GetPath() { return m_path; } | |||||
| Image const& GetImage() { return m_image; } | |||||
| protected: | |||||
| virtual bool DoWork() | |||||
| { | |||||
| return m_image.load(m_path); | |||||
| } | |||||
| std::string m_path; | |||||
| Image m_image; | |||||
| }; | |||||
| //----------------------------------------------------------------------------- | |||||
| Image* AsyncImageLoader::Load(std::string const &path) | |||||
| { | |||||
| //Create a job | |||||
| AsyncImageJob* job = new AsyncImageJob(path); | |||||
| //Create a dummy image | |||||
| Image* image = new Image(m_dummy_image); | |||||
| //Link the two | |||||
| m_images[path] = image; | |||||
| //Add job | |||||
| DispatchJob(job); | |||||
| //return Dummy image | |||||
| return image; | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| bool AsyncImageLoader::CheckStatus(Image* image) | |||||
| { | |||||
| if (m_loaded_images.find(image) != INDEX_NONE) | |||||
| { | |||||
| m_loaded_images.remove_item(image); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| //----------------------------------------------------------------------------- | |||||
| void AsyncImageLoader::TreatResult(ThreadJob* result) | |||||
| { | |||||
| AsyncImageJob* job = dynamic_cast<AsyncImageJob*>(result); | |||||
| ASSERT(job); | |||||
| //Copy image if work is OK | |||||
| if (job->GetJobType() == ThreadJobType::WORK_SUCCEEDED) | |||||
| { | |||||
| Image* src = m_images[job->GetPath()]; | |||||
| m_images.erase(job->GetPath()); | |||||
| src->Copy(job->GetImage()); | |||||
| m_loaded_images.push_unique(src); | |||||
| } | |||||
| else | |||||
| { | |||||
| msg::error("AsyncImageJob FAILED. See load image error above.\n"); | |||||
| } | |||||
| //Delete all that | |||||
| delete(result); | |||||
| } | |||||
| @@ -1,7 +1,7 @@ | |||||
| // | // | ||||
| // Lol Engine — Unit tests | // Lol Engine — Unit tests | ||||
| // | // | ||||
| // Copyright © 2010—2018 Sam Hocevar <sam@hocevar.net> | |||||
| // Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net> | |||||
| // © 2014—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> | // © 2014—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> | ||||
| // | // | ||||
| // Lol Engine is free software. It comes without any warranty, to | // Lol Engine is free software. It comes without any warranty, to | ||||
| @@ -23,130 +23,6 @@ namespace lol | |||||
| lolunit_declare_fixture(thread_test) | lolunit_declare_fixture(thread_test) | ||||
| { | { | ||||
| //FileUpdateTesterJob --------------------------------------------------------- | |||||
| class UnitTestJob : public ThreadJob | |||||
| { | |||||
| friend class UnitTestThreadManager; | |||||
| public: | |||||
| std::string GetName() const { return "<UnitTestJob>"; } | |||||
| UnitTestJob() | |||||
| : ThreadJob(ThreadJobType::WORK_TODO) | |||||
| { } | |||||
| bool IsDone() | |||||
| { | |||||
| return m_done; | |||||
| } | |||||
| protected: | |||||
| virtual bool DoWork() | |||||
| { | |||||
| timer t; | |||||
| m_done = false; | |||||
| msg::info("%s: STARTED WORK\n", GetName().c_str()); | |||||
| t.wait(2.f); | |||||
| msg::info("%s: ENDED WORK\n", GetName().c_str()); | |||||
| m_done = true; | |||||
| return true; | |||||
| } | |||||
| bool m_done = false; | |||||
| }; | |||||
| //Unit test thread manager | |||||
| class UnitTestThreadManager : public BaseThreadManager | |||||
| { | |||||
| typedef BaseThreadManager super; | |||||
| struct UnitTestStatusBase : public StructSafeEnum | |||||
| { | |||||
| enum Type | |||||
| { | |||||
| NOT_QUEUED, | |||||
| QUEUED, | |||||
| RETRIEVED, | |||||
| DONE, | |||||
| }; | |||||
| protected: | |||||
| virtual bool BuildEnumMap(std::map<int64_t, std::string>& enum_map) | |||||
| { | |||||
| enum_map[NOT_QUEUED] = "NOT_QUEUED"; | |||||
| enum_map[QUEUED] = "QUEUED"; | |||||
| enum_map[RETRIEVED] = "RETRIEVED"; | |||||
| enum_map[DONE] = "DONE"; | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| typedef SafeEnum<UnitTestStatusBase> UnitTestStatus; | |||||
| public: | |||||
| std::string GetName() const { return "<UnitTestThreadManager>"; } | |||||
| UnitTestThreadManager() : BaseThreadManager(4, 1) | |||||
| { } | |||||
| virtual ~UnitTestThreadManager() | |||||
| { } | |||||
| void AddJob(ThreadJob* job) | |||||
| { | |||||
| msg::info("%s DISPATCHING JOB %s\n", GetName().c_str(), job->GetName().c_str()); | |||||
| DispatchJob(job); | |||||
| } | |||||
| bool GetWorkResult(array<ThreadJob*>& results) | |||||
| { | |||||
| results += m_job_result; | |||||
| m_job_result.clear(); | |||||
| msg::info("%s GETWORKRESULT (%i)\n", GetName().c_str(), results.count()); | |||||
| return results.count() > 0; | |||||
| } | |||||
| virtual void tick_game(float seconds) | |||||
| { | |||||
| switch (m_status.ToScalar()) | |||||
| { | |||||
| case UnitTestStatus::NOT_QUEUED: | |||||
| if (!!GetDispatchCount()) | |||||
| { | |||||
| msg::info("%s TICKGAME %s\n", GetName().c_str(), m_status.tostring().c_str()); | |||||
| m_status = UnitTestStatus::QUEUED; | |||||
| } | |||||
| break; | |||||
| case UnitTestStatus::QUEUED: | |||||
| #if !defined(LOL_FEATURE_THREADS) || !LOL_FEATURE_THREADS | |||||
| if (!GetDispatchedCount()) | |||||
| #else //LOL_FEATURE_THREADS | |||||
| if (GetDispatchedCount()) | |||||
| #endif | |||||
| { | |||||
| msg::info("%s TICKGAME %s\n", GetName().c_str(), m_status.tostring().c_str()); | |||||
| m_status = UnitTestStatus::RETRIEVED; | |||||
| } | |||||
| break; | |||||
| case UnitTestStatus::RETRIEVED: | |||||
| if (m_job_result.count() == 4) | |||||
| { | |||||
| msg::info("%s TICKGAME %s\n", GetName().c_str(), m_status.tostring().c_str()); | |||||
| m_status = UnitTestStatus::DONE; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| //Debug error fix-up | |||||
| #if !LOL_BUILD_RELEASE | |||||
| m_tickstate = STATE_PRETICK_GAME; | |||||
| #endif | |||||
| super::tick_game(seconds); | |||||
| } | |||||
| bool IsDone() { return m_status == UnitTestStatus::DONE; } | |||||
| int Test_GetDispatchCount() { return GetDispatchCount(); } | |||||
| int Test_GetDispatchedCount() { return GetDispatchedCount(); } | |||||
| protected: | |||||
| virtual void TreatResult(ThreadJob* result) { m_job_result << result; } | |||||
| array<ThreadJob*> m_job_result; | |||||
| UnitTestStatus m_status; | |||||
| }; | |||||
| UnitTestThreadManager m_manager; | |||||
| void setup() | void setup() | ||||
| { | { | ||||
| } | } | ||||