diff --git a/.vscode/launch.json b/.vscode/launch.json index ad270ca..3ca231a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,6 +35,7 @@ "environment": [], "symbolSearchPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.25.28610\\lib\\x64", "requireExactSource": false, + "visualizerFile": "${workspaceRoot}/Seele.natvis", "logging": { "moduleLoad": false, "exceptions": true, diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cbbe02..d56a10a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ if(WIN32) endif() if(CMAKE_DEBUG_POSTFIX) add_compile_definitions(ENABLE_VALIDATION) + add_compile_definitions(SEELE_DEBUG) endif() add_executable(SeeleEngine "") target_link_libraries(SeeleEngine ${Vulkan_LIBRARY}) diff --git a/Seele.natvis b/Seele.natvis new file mode 100644 index 0000000..75c87c3 --- /dev/null +++ b/Seele.natvis @@ -0,0 +1,43 @@ + + + + {{ size={arraySize} }} + + arraySize + allocated + + arraySize + _data + + + + + {{ size={_size} }} + + _size + + _size + root + next + data + + + + + {{size={_size}} + + _size + comp + + nodeContainer.arraySize + nodeContainer._data + + + + + RefPtr {*object->handle} {object->refCount.load()} references + + object->handle + + + diff --git a/src/Engine/Containers/Array.natvis b/src/Engine/Containers/Array.natvis deleted file mode 100644 index d2620ed..0000000 --- a/src/Engine/Containers/Array.natvis +++ /dev/null @@ -1,13 +0,0 @@ - - - - {{ size={arraySize} }} - - arraySize - allocated - - arraySize - _data - - - diff --git a/src/Engine/Containers/CMakeLists.txt b/src/Engine/Containers/CMakeLists.txt index 24e2d04..93a7932 100644 --- a/src/Engine/Containers/CMakeLists.txt +++ b/src/Engine/Containers/CMakeLists.txt @@ -1,6 +1,5 @@ target_sources(SeeleEngine PRIVATE - Array.natvis Array.h Map.h List.h) \ No newline at end of file diff --git a/src/Engine/Scene/Components/MyComponent.cpp b/src/Engine/Scene/Components/MyComponent.cpp index 9d75e23..5a607b0 100644 --- a/src/Engine/Scene/Components/MyComponent.cpp +++ b/src/Engine/Scene/Components/MyComponent.cpp @@ -14,13 +14,17 @@ void MyComponent::start() Job MyComponent::tick(float deltatime) const { - writable++; + //std::cout << "MyComponent::tick" << std::endl; + ++writable; + //std::cout << "MyComponent::tick finished" << std::endl; otherComp->data = *writable; co_return; } Job MyComponent::update() { + //std::cout << "MyComponent::update" << std::endl; writable.update(); + //std::cout << "MyComponent::update finished" << std::endl; co_return; } diff --git a/src/Engine/Scene/Components/MyComponent.h b/src/Engine/Scene/Components/MyComponent.h index 0e5fa0c..c5522e3 100644 --- a/src/Engine/Scene/Components/MyComponent.h +++ b/src/Engine/Scene/Components/MyComponent.h @@ -13,7 +13,7 @@ public: virtual Job update(); private: Writable otherComp; - Writable writable = Writable(0); + Writable writable = 0; uint32 notWritable = 10; }; DECLARE_REF(MyComponent); diff --git a/src/Engine/Scene/Scene.cpp b/src/Engine/Scene/Scene.cpp index ce5778b..3cacb64 100644 --- a/src/Engine/Scene/Scene.cpp +++ b/src/Engine/Scene/Scene.cpp @@ -43,18 +43,30 @@ void Scene::start() Job Scene::beginUpdate(double deltaTime) { + //std::cout << "Scene::beginUpdate" << std::endl; + Array jobs; for(auto actor : rootActors) { - co_await Job::all(actor->launchTick(static_cast(deltaTime))); + jobs.addAll(actor->launchTick(static_cast(deltaTime))); + } + co_await Job::all(jobs); + //std::cout << "Scene::beginUpdate finished waiting" << std::endl; + for(auto job : jobs) + { + assert(job.done()); } } Job Scene::commitUpdate() { + //std::cout << "Scene::commitUpdate" << std::endl; + Array jobs; for(auto actor : rootActors) { - co_await Job::all(actor->launchUpdate()); + jobs.addAll(actor->launchUpdate()); } + co_await Job::all(jobs); + //std::cout << "Scene::commitUpdate finished waiting" << std::endl; } void Scene::addActor(PActor actor) diff --git a/src/Engine/Scene/Util.h b/src/Engine/Scene/Util.h index 3c8b191..15cd902 100644 --- a/src/Engine/Scene/Util.h +++ b/src/Engine/Scene/Util.h @@ -9,15 +9,15 @@ class Writable public: Writable() {} - explicit Writable(T initialData) + Writable(T initialData) : data(initialData) , toBeWritten(initialData) {} Writable(const Writable& other) = delete; Writable(Writable&& other) = default; ~Writable() = default; - Writable& operator=(const Writable& other) = delete; - Writable& operator=(Writable&& other) = default; + Writable& operator=(const Writable& other) const = delete; + Writable& operator=(Writable&& other) const = delete; const Writable& operator=(const T& other) const { deferWrite(other); diff --git a/src/Engine/ThreadPool.cpp b/src/Engine/ThreadPool.cpp index 9638f20..e4c72a0 100644 --- a/src/Engine/ThreadPool.cpp +++ b/src/Engine/ThreadPool.cpp @@ -6,7 +6,7 @@ using namespace Seele; std::atomic_uint64_t Seele::globalCounter; Event::Event() - : flag(std::make_shared()) + : flag(std::make_shared()) { } @@ -17,23 +17,31 @@ Event::Event(nullptr_t) Event::Event(const std::string &name) : name(name) - , flag(std::make_shared()) + , flag(std::make_shared()) { } void Event::raise() { - flag->store(1); + std::scoped_lock lock(flag->lock); + flag->data = 1; getGlobalThreadPool().notify(*this); } void Event::reset() { - flag->store(0); + std::scoped_lock lock(flag->lock); + flag->data = 0; } bool Event::await_ready() { - return flag->load(); + flag->lock.lock(); + bool result = flag->data; + if(result) + { + flag->lock.unlock(); + } + return result; } ThreadPool::ThreadPool(uint32 threadCount) @@ -48,7 +56,15 @@ ThreadPool::ThreadPool(uint32 threadCount) ThreadPool::~ThreadPool() { - running.store(false); + cleanup(); +} + +void ThreadPool::cleanup() +{ + bool temp = true; + running.compare_exchange_strong(temp, false); + if(!temp) + return; { std::scoped_lock lock(jobQueueLock); jobQueueCV.notify_all(); @@ -71,6 +87,7 @@ void ThreadPool::enqueueWaiting(Event &event, Promise* job) std::scoped_lock lock(waitingLock); //std::cout << "Job " << job->finishedEvent.name << " waiting on event " << event.name << std::endl; waitingJobs[event].push_back(job); + job->addRef(); } void ThreadPool::enqueueWaiting(Event &event, MainPromise* job) { @@ -78,6 +95,7 @@ void ThreadPool::enqueueWaiting(Event &event, MainPromise* job) std::scoped_lock lock(waitingMainLock); //std::cout << job->finishedEvent.name << " waiting on event " << event.name << std::endl; waitingMainJobs[event].push_back(job); + job->addRef(); } void ThreadPool::scheduleJob(Promise* job) { @@ -86,6 +104,7 @@ void ThreadPool::scheduleJob(Promise* job) //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; jobQueue.push_back(job); jobQueueCV.notify_one(); + job->addRef(); } void ThreadPool::scheduleJob(MainPromise* job) { @@ -94,6 +113,7 @@ void ThreadPool::scheduleJob(MainPromise* job) //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; mainJobs.push_back(job); mainJobCV.notify_one(); + job->addRef(); } void ThreadPool::notify(Event &event) { @@ -149,10 +169,7 @@ void ThreadPool::threadLoop(const bool mainThread) } } job->resume(); - if (job->done()) - { - job->finalize(); - } + job->removeRef(); } else { @@ -175,14 +192,10 @@ void ThreadPool::threadLoop(const bool mainThread) } //std::cout << "Starting job " << job.id << std::endl; job->resume(); - if(job->done()) - { - job->finalize(); - } + job->removeRef(); } } } - ThreadPool &Seele::getGlobalThreadPool() { static ThreadPool threadPool; diff --git a/src/Engine/ThreadPool.h b/src/Engine/ThreadPool.h index 2a908a2..fa74056 100644 --- a/src/Engine/ThreadPool.h +++ b/src/Engine/ThreadPool.h @@ -31,7 +31,8 @@ public: } operator bool() { - return flag->load(); + std::scoped_lock lock(flag->lock); + return flag->data; } void raise(); @@ -42,12 +43,17 @@ public: constexpr void await_resume() {} private: std::string name; - std::shared_ptr flag; + struct StateStore + { + std::mutex lock; + bool data; + }; + std::shared_ptr flag; friend class ThreadPool; }; extern std::atomic_uint64_t globalCounter; -template +template struct JobBase; template struct JobPromiseBase @@ -66,26 +72,22 @@ struct JobPromiseBase id = globalCounter++; finishedEvent = Event(std::format("Job {}", id)); } - ~JobPromiseBase() - { - if (handle) - { - handle.destroy(); - } - } + ~JobPromiseBase() + {} JobBase get_return_object() noexcept; inline auto initial_suspend() noexcept; - inline auto final_suspend() const noexcept; + inline auto final_suspend() noexcept; void return_void() noexcept {} void unhandled_exception() noexcept { - std::cerr << "Unhandled exception" << std::endl; + std::cerr << "Unhandled exception! Exiting" << std::endl; exit(1); }; void resume() { + std::scoped_lock lock(promiseLock); if(!handle || handle.done() || executing()) { return; @@ -93,15 +95,26 @@ struct JobPromiseBase state = State::EXECUTING; handle.resume(); } + void markDone() + { + state = State::DONE; + finishedEvent.raise(); + if(continuation) + { + getGlobalThreadPool().scheduleJob(continuation); + continuation->removeRef(); + } + } void setContinuation(JobPromiseBase* cont) { std::scoped_lock lock(promiseLock, cont->promiseLock); - continuation = cont->handle; + continuation = cont; cont->state = State::SCHEDULED; + cont->addRef(); } bool done() { - return handle.done(); + return state == State::DONE; } bool scheduled() { @@ -119,12 +132,6 @@ struct JobPromiseBase { return state == State::READY; } - void finalize() - { - std::scoped_lock lock(promiseLock); - state = State::DONE; - finishedEvent.raise(); - } void reset() { std::scoped_lock lock(promiseLock); @@ -132,7 +139,6 @@ struct JobPromiseBase } void enqueue(Event& event) { - // no need to lock here, as it is only called while executing, where the lock is already held if(!handle || handle.done() || waiting() || scheduled()) { return; @@ -142,6 +148,7 @@ struct JobPromiseBase } void schedule() { + //dont lock here, as it is already locked from outside std::scoped_lock lock(promiseLock); if(!handle || done() || !ready()) { @@ -149,11 +156,23 @@ struct JobPromiseBase } getGlobalThreadPool().scheduleJob(this); } + void addRef() + { + numRefs++; + } + void removeRef() + { + if(--numRefs < 1 && done()) + { + handle.destroy(); + } + } std::mutex promiseLock; std::coroutine_handle handle; - std::coroutine_handle<> continuation = std::noop_coroutine(); + JobPromiseBase* continuation = nullptr; uint64 id; + std::atomic_uint64_t numRefs = 0; Event finishedEvent; State state = State::READY; }; @@ -172,13 +191,46 @@ public: : promise(promise) { } + JobBase(const JobBase& other) + { + std::scoped_lock lock(other.promise->promiseLock); + promise = other.promise; + promise->addRef(); + } + JobBase(JobBase&& other) + { + std::scoped_lock lock(other.promise->promiseLock); + promise = other.promise; + other.promise = nullptr; + } ~JobBase() { if(promise) { promise->schedule(); + promise->removeRef(); } } + JobBase& operator=(const JobBase& other) + { + if(this != &other) + { + std::scoped_lock lock(other.promise->promiseLock); + promise = other.promise; + promise->addRef(); + } + return *this; + } + JobBase& operator=(JobBase&& other) + { + if(this != &other) + { + std::scoped_lock lock(other.promise->promiseLock); + promise = other.promise; + other.promise = nullptr; + } + return *this; + } void resume() { promise->resume(); @@ -203,23 +255,32 @@ public: } Event operator co_await() { + Event result = promise->finishedEvent; + // if we schedule the promise now, it might finish and self destruct + // but there is no way for us to know that + // but as the co_await operator keeps a reference to this, we it won't + // be scheduled from the destructor promise->schedule(); - return promise->finishedEvent; + // so we reset the promise so we don't schedule it when this destructs + promise->removeRef(); + promise = nullptr; + // but we still have to provide with the event, so we get that first + return result; } static JobBase all() = delete; - template - static JobBase all(Awaitable... jobs) - { - (co_await jobs, ...); - } template - static JobBase all(Iterable&& collection) + static JobBase all(Iterable collection) { - for(auto&& it : collection) + for(auto it : collection) { co_await it; } } + template + static JobBase all(Awaitable&&... jobs) + { + return JobBase::all(Array{jobs...}); + } template static JobBase launchJobs(JobFunc&& func, IterableParams params) { @@ -245,8 +306,9 @@ using Promise = JobPromiseBase; class ThreadPool { public: - ThreadPool(uint32 threadCount = 1); + ThreadPool(uint32 threadCount = std::thread::hardware_concurrency()); virtual ~ThreadPool(); + void cleanup(); // Adds a job to the waiting queue for event void enqueueWaiting(Event& event, Promise* job); // Adds a job to the waiting queue for event @@ -277,6 +339,7 @@ private: template inline JobBase JobPromiseBase::get_return_object() noexcept { + numRefs++; return JobBase(this); } @@ -287,24 +350,29 @@ inline auto JobPromiseBase::initial_suspend() noexcept } template -inline auto JobPromiseBase::final_suspend() const noexcept +inline auto JobPromiseBase::final_suspend() noexcept { - struct JobAwaitable + /*struct JobAwaitable { constexpr bool await_ready() const noexcept { return false; } constexpr auto await_suspend(std::coroutine_handle> h) const noexcept { - return h.promise().continuation; + auto continuation = h.promise().continuation; + h.destroy(); + } constexpr void await_resume() const noexcept {} }; - return JobAwaitable{}; + return JobAwaitable{};*/ + markDone(); + return std::suspend_always{}; } template inline constexpr void Event::await_suspend(std::coroutine_handle> h) { h.promise().enqueue(*this); + flag->lock.unlock(); } } // namespace Seele diff --git a/src/Engine/Window/SceneView.cpp b/src/Engine/Window/SceneView.cpp index a04d8e6..c59ca9b 100644 --- a/src/Engine/Window/SceneView.cpp +++ b/src/Engine/Window/SceneView.cpp @@ -40,12 +40,15 @@ Seele::SceneView::SceneView(Gfx::PGraphics graphics, PWindow owner, const Viewpo plane->setWorldScale(Vector(100, 100, 100)); scene->addPrimitiveComponent(plane); - PMyComponent myComp = new MyComponent(); - PMyOtherComponent myOtherComp = new MyOtherComponent(); - PActor actor = new Actor(); - actor->setRootComponent(myComp); - myComp->addChildComponent(myOtherComp); - scene->addActor(actor); + for(uint32 i = 0; i < 1; ++i) + { + PMyComponent myComp = new MyComponent(); + PMyOtherComponent myOtherComp = new MyOtherComponent(); + PActor actor = new Actor(); + actor->setRootComponent(myComp); + myComp->addChildComponent(myOtherComp); + scene->addActor(actor); + } scene->start(); PRenderGraphResources resources = new RenderGraphResources(); diff --git a/src/Engine/Window/Window.cpp b/src/Engine/Window/Window.cpp index e2c9574..1719101 100644 --- a/src/Engine/Window/Window.cpp +++ b/src/Engine/Window/Window.cpp @@ -75,8 +75,8 @@ void Window::setFocused(PView view) Job Window::viewWorker(size_t viewIndex) { WindowView* windowView = views[viewIndex]; - windowView->view->beginUpdate(); - windowView->view->update(); + co_await windowView->view->beginUpdate(); + co_await windowView->view->update(); { std::scoped_lock lock(windowView->workerMutex); windowView->view->commitUpdate(); diff --git a/test/Engine/EngineTest.h b/test/Engine/EngineTest.h index e525c5c..5054fa9 100644 --- a/test/Engine/EngineTest.h +++ b/test/Engine/EngineTest.h @@ -1,5 +1,5 @@ #pragma once -#include +//#include #include "ThreadPool.h" namespace Seele @@ -8,10 +8,11 @@ namespace Seele { GlobalFixture() { - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + //_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); } ~GlobalFixture() { + getGlobalThreadPool().cleanup(); } void setup() { diff --git a/test/Engine/ThreadPool.cpp b/test/Engine/ThreadPool.cpp index 40b13eb..bc72248 100644 --- a/test/Engine/ThreadPool.cpp +++ b/test/Engine/ThreadPool.cpp @@ -4,51 +4,107 @@ BOOST_AUTO_TEST_SUITE(ThreadPool) -uint64 basicJobState = 0; +uint64 basicAwaitState = 0; + +Job basicAwaitFirst() +{ + BOOST_REQUIRE_EQUAL(basicAwaitState, 5); + basicAwaitState = 10; + co_return; +} +Job basicAwaitSecond() +{ + BOOST_REQUIRE_EQUAL(basicAwaitState, 15); + basicAwaitState = 20; + co_return; +} +Job basicAwaitThird() +{ + BOOST_REQUIRE_EQUAL(basicAwaitState, 25); + basicAwaitState = 30; + co_return; +} + +Job basicAwaitBase() +{ + basicAwaitState = 5; + co_await basicAwaitFirst(); + BOOST_REQUIRE_EQUAL(basicAwaitState, 10); + basicAwaitState = 15; + co_await basicAwaitSecond(); + BOOST_REQUIRE_EQUAL(basicAwaitState, 20); + basicAwaitState = 25; + co_await basicAwaitThird(); + BOOST_REQUIRE_EQUAL(basicAwaitState, 30); +} + +BOOST_AUTO_TEST_CASE(basic_coawait) +{ + basicAwaitBase(); +} + +uint64 basicThenState = 5; Job basicThenFirst() { - basicJobState = 10; + BOOST_REQUIRE_EQUAL(basicThenState, 5); + basicThenState = 10; co_return; } Job basicThenSecond() { - BOOST_REQUIRE_EQUAL(basicJobState, 10); - basicJobState = 20; + BOOST_REQUIRE_EQUAL(basicThenState, 15); + basicThenState = 20; co_return; } Job basicThenThird() { - BOOST_REQUIRE_EQUAL(basicJobState, 20); + BOOST_REQUIRE_EQUAL(basicThenState, 25); + basicThenState = 30; co_return; } -Job basicThenBase() +BOOST_AUTO_TEST_CASE(basic_thenchain) { - co_await basicThenFirst(); - co_await basicThenSecond(); - co_await basicThenThird(); + basicThenFirst() + .then([]() -> Job + { + BOOST_REQUIRE_EQUAL(basicThenState, 10); + basicThenState = 15; + co_return; + }) + .then(basicThenSecond()) + .then([]() -> Job + { + BOOST_REQUIRE_EQUAL(basicThenState, 20); + basicThenState = 25; + co_return; + }) + .then(basicThenThird()) + .then([]() -> Job + { + BOOST_REQUIRE_EQUAL(basicThenState, 30); + co_return; + }); } -BOOST_AUTO_TEST_CASE(basic_then) -{ - basicThenBase(); -} -/* uint64 basicAllState1 = 0; uint64 basicAllState2 = 0; Job basicAllFirst() { basicAllState1 = 10; + std::cout << "AllFirst" << std::endl; co_return; } Job basicAllSecond() { basicAllState2 = 10; + std::cout << "AllSecond" << std::endl; co_return; } Job basicAllThen() { + std::cout << "AllThen" << std::endl; BOOST_REQUIRE_EQUAL(basicAllState1, 10); BOOST_REQUIRE_EQUAL(basicAllState2, 10); co_return; @@ -70,10 +126,12 @@ Job basicCallableFunc() BOOST_AUTO_TEST_CASE(basic_callable) { basicCallableFunc() - .then([=]() -> Job{ + .then([=]() -> Job + { BOOST_REQUIRE_EQUAL(basicCallable, 10); + std::cout << "callable" << std::endl; co_return; }); -}*/ +} BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file