diff --git a/CMakeLists.txt b/CMakeLists.txt index 02aec92..7dd8338 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,8 @@ target_sources(nf7 main.cc nf7.cc nf7.hh + + common/queue.hh ) target_link_libraries(nf7 PRIVATE diff --git a/common/queue.hh b/common/queue.hh new file mode 100644 index 0000000..ae75215 --- /dev/null +++ b/common/queue.hh @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace nf7 { + +// thread-safe std::deque wrapper +template +class Queue { + public: + Queue() = default; + Queue(const Queue&) = default; + Queue(Queue&&) = default; + Queue& operator=(const Queue&) = default; + Queue& operator=(Queue&&) = default; + + void Push(T&& task) noexcept { + std::unique_lock _(mtx_); + tasks_.push_back(std::move(task)); + } + std::optional Pop() noexcept { + std::unique_lock k(mtx_); + if (tasks_.empty()) return std::nullopt; + auto ret = std::move(tasks_.front()); + tasks_.pop_front(); + k.unlock(); + return ret; + } + + protected: + std::mutex mtx_; + + private: + std::deque tasks_; +}; + +// Queue with Wait() method +template +class WaitQueue : private Queue { + public: + WaitQueue() = default; + WaitQueue(const WaitQueue&) = default; + WaitQueue(WaitQueue&&) = default; + WaitQueue& operator=(const WaitQueue&) = default; + WaitQueue& operator=(WaitQueue&&) = default; + + void Push(T&& task) noexcept { + Queue::Push(std::move(task)); + cv_.notify_all(); + } + using Queue::Pop; + + void Notify() noexcept { + cv_.notify_all(); + } + + void Wait() noexcept { + std::unique_lock k(mtx_); + cv_.wait(k); + } + void WaitFor(auto dur) noexcept { + std::unique_lock k(mtx_); + cv_.wait_for(k, dur); + } + void WaitUntil(auto time) noexcept { + std::unique_lock k(mtx_); + cv_.wait_until(k, time); + } + + private: + using Queue::mtx_; + + std::condition_variable cv_; +}; + +} // namespace nf7 diff --git a/main.cc b/main.cc index 9481a07..e88206b 100644 --- a/main.cc +++ b/main.cc @@ -1,3 +1,7 @@ +#include +#include +#include + #include #include #include @@ -6,9 +10,182 @@ #include "nf7.hh" +#include "common/queue.hh" + #include +using namespace nf7; + +class Env final : public nf7::Env { + public: + static constexpr size_t kSubTaskUnit = 64; + + Env() noexcept : nf7::Env(std::filesystem::current_path()) { + // start threads + main_thread_ = std::thread([this]() { MainThread(); }); + async_threads_.resize(std::max(std::thread::hardware_concurrency(), 2)); + for (auto& th : async_threads_) { + th = std::thread([this]() { AsyncThread(); }); + } + } + ~Env() noexcept { + alive_ = false; + cv_.notify_one(); + async_.Notify(); + + main_thread_.join(); + for (auto& th : async_threads_) th.join(); + } + + void ExecMain(Context::Id, Task&& task) noexcept override { + main_.Push(std::move(task)); + } + void ExecSub(Context::Id, Task&& task) noexcept override { + sub_.Push(std::move(task)); + } + void ExecAsync(Context::Id, Task&& task) noexcept override { + async_.Push(std::move(task)); + } + + void Update() noexcept { + interrupt_ = true; + std::unique_lock _(mtx_); + + ImGui::PushID(this); + UpdatePanic(); + ImGui::PopID(); + + cv_.notify_one(); + } + + protected: + File& GetFile(File::Id id) const override { + auto itr = files_.find(id); + if (itr == files_.end()) { + throw ExpiredException("file ("+std::to_string(id)+") is expired"); + } + return *itr->second; + } + File::Id AddFile(File& f) noexcept override { + auto [itr, ok] = files_.emplace(file_next_++, &f); + assert(ok); + return itr->first; + } + void RemoveFile(File::Id id) noexcept override { + files_.erase(id); + } + + Context& GetContext(Context::Id id) const override { + auto itr = ctxs_.find(id); + if (itr == ctxs_.end()) { + throw ExpiredException("context ("+std::to_string(id)+") is expired"); + } + return *itr->second; + } + Context::Id AddContext(Context& ctx) noexcept override { + auto [itr, ok] = ctxs_.emplace(ctx_next_++, &ctx); + assert(ok); + return itr->first; + } + void RemoveContext(Context::Id id) noexcept override { + ctxs_.erase(id); + } + + private: + std::atomic alive_ = true; + std::exception_ptr panic_; + + File::Id file_next_ = 1; + std::unordered_map files_; + + Context::Id ctx_next_ = 1; + std::unordered_map ctxs_; + + Queue main_; + Queue sub_; + WaitQueue async_; + + std::mutex mtx_; + std::condition_variable cv_; + std::atomic interrupt_; + std::thread main_thread_; + std::vector async_threads_; + + + void Panic(std::exception_ptr ptr = std::current_exception()) noexcept { + panic_ = ptr; + } + void UpdatePanic() noexcept { + if (ImGui::BeginPopup("panic")) { + ImGui::TextUnformatted("something went wrong X("); + + ImGui::BeginGroup(); + { + auto ptr = panic_; + while (ptr) + try { + std::rethrow_exception(ptr); + } catch (Exception& e) { + e.UpdatePanic(); + } + } + ImGui::EndGroup(); + + if (ImGui::Button("abort")) { + std::abort(); + } + ImGui::SameLine(); + if (ImGui::Button("ignore")) { + panic_ = {}; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + if (panic_) ImGui::OpenPopup("panic"); + } + } + + + void MainThread() noexcept { + std::unique_lock k(mtx_); + while (alive_) { + // exec main tasks + while (auto task = main_.Pop()) + try { + (*task)(); + } catch (Exception&) { + Panic(); + } + + // exec sub tasks until interrupted + while (!interrupt_) { + for (size_t i = 0; i < kSubTaskUnit; ++i) { + const auto task = sub_.Pop(); + if (!task) break; + try { + (*task)(); + } catch (Exception&) { + Panic(); + } + } + } + cv_.wait(k); + } + } + void AsyncThread() noexcept { + while (alive_) { + while (auto task = async_.Pop()) + try { + (*task)(); + } catch (Exception&) { + Panic(); + } + async_.Wait(); + } + } +}; + int main(int, char**) { // init display glfwSetErrorCallback( @@ -52,27 +229,30 @@ int main(int, char**) { ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); - // main loop - while (true) { - // new frame - glfwPollEvents(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + // main logic + { + ::Env env; + glfwShowWindow(window); + while (!glfwWindowShouldClose(window)) { + // new frame + glfwPollEvents(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); - // TODO: update + env.Update(); - // render windows - ImGui::Render(); - int w, h; - glfwGetFramebufferSize(window, &w, &h); - glViewport(0, 0, w, h); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); - - // TODO: handle queue + // render windows + ImGui::Render(); + int w, h; + glfwGetFramebufferSize(window, &w, &h); + glViewport(0, 0, w, h); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + glfwSwapBuffers(window); + } } + // teardown ImGUI ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); diff --git a/nf7.hh b/nf7.hh index a940fd6..7a61d09 100644 --- a/nf7.hh +++ b/nf7.hh @@ -182,8 +182,6 @@ class Env { public: class Watcher; - using Task = std::function; - Env() = delete; Env(const std::filesystem::path& npath) noexcept : npath_(npath) { } @@ -193,21 +191,26 @@ class Env { Env& operator=(const Env&) = delete; Env& operator=(Env&&) = delete; - virtual File::Id AddFile(File&) noexcept = 0; - virtual File& RemoveFile(File::Id) noexcept = 0; - virtual File& GetFile(File::Id) = 0; + virtual File& GetFile(File::Id) const = 0; + virtual Context& GetContext(Context::Id) const = 0; - virtual Context::Id AddContext(Context&) noexcept = 0; - virtual Context& RemoveContext(Context::Id) noexcept = 0; - virtual Context& GetContext(Context::Id) = 0; - - // thread-safe + // all Exec*() methods are thread-safe + using Task = std::function; virtual void ExecMain(Context::Id, Task&&) noexcept = 0; virtual void ExecSub(Context::Id, Task&&) noexcept = 0; virtual void ExecAsync(Context::Id, Task&&) noexcept = 0; const std::filesystem::path& npath() const noexcept { return npath_; } + protected: + friend class File; + virtual File::Id AddFile(File&) noexcept = 0; + virtual void RemoveFile(File::Id) noexcept = 0; + + friend class Context; + virtual Context::Id AddContext(Context&) noexcept = 0; + virtual void RemoveContext(Context::Id) noexcept = 0; + private: std::filesystem::path npath_; };