Merge pull request 'feat/165-gui' (#168) from feat/165-gui into main
Reviewed-on: #168
This commit is contained in:
commit
ab9e7f4a3d
@ -6,6 +6,7 @@ target_link_libraries(nf7_core
|
|||||||
luajit
|
luajit
|
||||||
nf7_config
|
nf7_config
|
||||||
nf7_iface
|
nf7_iface
|
||||||
|
OpenGL
|
||||||
SDL2
|
SDL2
|
||||||
sqlite
|
sqlite
|
||||||
uvw
|
uvw
|
||||||
@ -13,6 +14,7 @@ target_link_libraries(nf7_core
|
|||||||
target_sources(nf7_core
|
target_sources(nf7_core
|
||||||
PRIVATE
|
PRIVATE
|
||||||
gl3/context.cc
|
gl3/context.cc
|
||||||
|
imgui/context.cc
|
||||||
luajit/context.cc
|
luajit/context.cc
|
||||||
luajit/lambda.cc
|
luajit/lambda.cc
|
||||||
luajit/thread.cc
|
luajit/thread.cc
|
||||||
@ -22,6 +24,8 @@ target_sources(nf7_core
|
|||||||
version.cc
|
version.cc
|
||||||
PUBLIC
|
PUBLIC
|
||||||
gl3/context.hh
|
gl3/context.hh
|
||||||
|
imgui/context.hh
|
||||||
|
imgui/driver.hh
|
||||||
luajit/context.hh
|
luajit/context.hh
|
||||||
luajit/lambda.hh
|
luajit/lambda.hh
|
||||||
luajit/thread.hh
|
luajit/thread.hh
|
||||||
@ -44,6 +48,9 @@ add_executable(nf7_core_test)
|
|||||||
target_sources(nf7_core_test
|
target_sources(nf7_core_test
|
||||||
PRIVATE
|
PRIVATE
|
||||||
gl3/context_test.cc
|
gl3/context_test.cc
|
||||||
|
imgui/context_test.cc
|
||||||
|
imgui/context_test.hh
|
||||||
|
imgui/driver_test.hh
|
||||||
luajit/context_test.cc
|
luajit/context_test.cc
|
||||||
luajit/context_test.hh
|
luajit/context_test.hh
|
||||||
luajit/lambda_test.cc
|
luajit/lambda_test.cc
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
@ -24,15 +25,22 @@ namespace nf7::core::test {
|
|||||||
|
|
||||||
class EnvFixture : public ::testing::Test {
|
class EnvFixture : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
EnvFixture() = delete;
|
EnvFixture() = default;
|
||||||
explicit EnvFixture(SimpleEnv::FactoryMap&& fmap) noexcept
|
|
||||||
: fmap_(std::move(fmap)) { }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template <typename I>
|
template <typename I>
|
||||||
void Install(const std::shared_ptr<I>& o) {
|
void Install(const std::shared_ptr<I>& o) {
|
||||||
fmap_.emplace(typeid(I), [o](auto&) { return o; });
|
fmap_.emplace(typeid(I), [o](auto&) { return o; });
|
||||||
}
|
}
|
||||||
|
template <typename I>
|
||||||
|
void Install(std::function<std::shared_ptr<I>(Env&)>&& factory) {
|
||||||
|
fmap_.emplace(typeid(I), std::move(factory));
|
||||||
|
}
|
||||||
|
template <typename I, typename T>
|
||||||
|
void Install() {
|
||||||
|
fmap_.emplace(
|
||||||
|
typeid(I), [](auto& env) { return std::make_shared<T>(env); });
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
@ -120,9 +128,8 @@ class EnvFixtureWithTasking : public EnvFixture {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EnvFixtureWithTasking(SimpleEnv::FactoryMap&& fmap = {})
|
EnvFixtureWithTasking()
|
||||||
: EnvFixture(std::move(fmap)),
|
: clock_(std::make_shared<Clock>()),
|
||||||
clock_(std::make_shared<Clock>()),
|
|
||||||
sq_(std::make_shared<SimpleTaskQueue<SyncTask>>()),
|
sq_(std::make_shared<SimpleTaskQueue<SyncTask>>()),
|
||||||
aq_(std::make_shared<SimpleTaskQueue<AsyncTask>>()),
|
aq_(std::make_shared<SimpleTaskQueue<AsyncTask>>()),
|
||||||
ad_(*this) {
|
ad_(*this) {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
#include "core/gl3/context.hh"
|
#include "core/gl3/context.hh"
|
||||||
|
|
||||||
#include "iface/common/exception.hh"
|
#include "iface/common/exception.hh"
|
||||||
|
#include "iface/subsys/clock.hh"
|
||||||
|
#include "iface/subsys/logger.hh"
|
||||||
#include "iface/subsys/parallelism.hh"
|
#include "iface/subsys/parallelism.hh"
|
||||||
|
|
||||||
#include "core/logger.hh"
|
#include "core/logger.hh"
|
||||||
@ -11,138 +13,151 @@ using namespace std::literals;
|
|||||||
|
|
||||||
namespace nf7::core::gl3 {
|
namespace nf7::core::gl3 {
|
||||||
|
|
||||||
Context::Context(Env& env, const std::shared_ptr<Impl>& impl)
|
class Context::Impl : public std::enable_shared_from_this<Impl> {
|
||||||
|
private:
|
||||||
|
static constexpr const auto kPollingInterval = 17ms;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Impl(Env& env, Context& ctx)
|
||||||
|
try : clock_(env.Get<subsys::Clock>()),
|
||||||
|
concurrency_(env.Get<subsys::Concurrency>()),
|
||||||
|
logger_(env.GetOr<subsys::Logger>(NullLogger::kInstance)),
|
||||||
|
ctx_(&ctx) {
|
||||||
|
sdl_ = 0 == SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
if (!sdl_) {
|
||||||
|
throw Exception {"SDL init failure"};
|
||||||
|
}
|
||||||
|
SetUpGL();
|
||||||
|
SetUpWindow();
|
||||||
|
} catch (const Exception&) {
|
||||||
|
TearDown();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~Impl() = default;
|
||||||
|
Impl(const Impl&) = delete;
|
||||||
|
Impl(Impl&&) = delete;
|
||||||
|
Impl& operator=(const Impl&) = delete;
|
||||||
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TaskContext MakeContext() const noexcept {
|
||||||
|
return TaskContext {win_, gl_};
|
||||||
|
}
|
||||||
|
void SchedulePolling() noexcept {
|
||||||
|
if (nullptr != ctx_) {
|
||||||
|
concurrency_->Push(nf7::SyncTask {
|
||||||
|
clock_->now() + kPollingInterval,
|
||||||
|
[this, self = shared_from_this()](auto&) { Poll(); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ScheduleTearDown() noexcept {
|
||||||
|
ctx_ = nullptr;
|
||||||
|
concurrency_->Exec(
|
||||||
|
[this, self = shared_from_this()](auto&) { TearDown(); });
|
||||||
|
}
|
||||||
|
void Push(Task&& task) noexcept {
|
||||||
|
const auto after = task.after();
|
||||||
|
concurrency_->Push(SyncTask {
|
||||||
|
after,
|
||||||
|
[this, self = shared_from_this(), task = std::move(task)](auto&) mutable {
|
||||||
|
auto ctx = MakeContext();
|
||||||
|
task(ctx);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Poll() noexcept {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (nullptr != ctx_) {
|
||||||
|
ctx_->Notify(SDL_Event {e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SchedulePolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetUpGL() noexcept {
|
||||||
|
# if defined(__APPLE__)
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||||
|
# else
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
void SetUpWindow() {
|
||||||
|
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
|
constexpr auto flags {
|
||||||
|
SDL_WINDOW_OPENGL
|
||||||
|
| SDL_WINDOW_RESIZABLE
|
||||||
|
| SDL_WINDOW_ALLOW_HIGHDPI
|
||||||
|
};
|
||||||
|
win_ = SDL_CreateWindow(
|
||||||
|
"Dear ImGui SDL2+OpenGL3 example",
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
1280, 720,
|
||||||
|
flags);
|
||||||
|
if (nullptr == win_) {
|
||||||
|
throw Exception {"failed to create new window"};
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_ = SDL_GL_CreateContext(win_);
|
||||||
|
if (nullptr == gl_) {
|
||||||
|
throw Exception {"failed to create new GL context"};
|
||||||
|
}
|
||||||
|
SDL_GL_SetSwapInterval(0);
|
||||||
|
}
|
||||||
|
void TearDown() noexcept {
|
||||||
|
if (nullptr != gl_) {
|
||||||
|
SDL_GL_DeleteContext(gl_);
|
||||||
|
}
|
||||||
|
if (nullptr != win_) {
|
||||||
|
SDL_DestroyWindow(win_);
|
||||||
|
}
|
||||||
|
if (sdl_) {
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::shared_ptr<subsys::Clock> clock_;
|
||||||
|
const std::shared_ptr<subsys::Concurrency> concurrency_;
|
||||||
|
const std::shared_ptr<subsys::Logger> logger_;
|
||||||
|
|
||||||
|
Context* ctx_;
|
||||||
|
|
||||||
|
bool sdl_ {false};
|
||||||
|
SDL_Window* win_ {nullptr};
|
||||||
|
void* gl_ {nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
Context::Context(Env& env)
|
||||||
try : subsys::Interface("nf7::core::gl3::Context"),
|
try : subsys::Interface("nf7::core::gl3::Context"),
|
||||||
impl_(impl? impl: std::make_shared<Impl>(env)) {
|
impl_(std::make_shared<Impl>(env, *this)) {
|
||||||
impl_->Main();
|
impl_->SchedulePolling();
|
||||||
} catch (const std::bad_alloc&) {
|
} catch (const std::bad_alloc&) {
|
||||||
throw MemoryException {};
|
throw MemoryException {};
|
||||||
}
|
}
|
||||||
Context::~Context() noexcept {
|
Context::~Context() noexcept {
|
||||||
impl_->Exit();
|
impl_->ScheduleTearDown();
|
||||||
}
|
}
|
||||||
void Context::Push(Task&& task) noexcept {
|
void Context::Push(Task&& task) noexcept {
|
||||||
impl_->Push(std::move(task));
|
impl_->Push(std::move(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Context::Impl::TaskDriver final {
|
|
||||||
public:
|
|
||||||
TaskDriver() = delete;
|
|
||||||
explicit TaskDriver(const std::shared_ptr<subsys::Logger>& logger,
|
|
||||||
Task::Time time) noexcept
|
|
||||||
: logger_(logger), time_(time) { }
|
|
||||||
|
|
||||||
public:
|
|
||||||
void BeginBusy() noexcept { }
|
|
||||||
void EndBusy() noexcept { }
|
|
||||||
|
|
||||||
void Drive(Task&& task) noexcept
|
|
||||||
try {
|
|
||||||
task(ctx_);
|
|
||||||
} catch (Exception& e) {
|
|
||||||
logger_->Warn("GL3 task caused an exception");
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Task::Time tick() const noexcept { return time_; }
|
|
||||||
bool nextIdleInterruption() const noexcept { return true; }
|
|
||||||
bool nextTaskInterruption() const noexcept { return false; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::shared_ptr<subsys::Logger> logger_;
|
|
||||||
const Task::Time time_;
|
|
||||||
|
|
||||||
TaskContext ctx_ {};
|
|
||||||
};
|
|
||||||
|
|
||||||
Context::Impl::Impl(Env& env)
|
|
||||||
try : clock_(env.Get<subsys::Clock>()),
|
|
||||||
concurrency_(env.Get<subsys::Concurrency>()),
|
|
||||||
logger_(env.GetOr<subsys::Logger>(NullLogger::kInstance)) {
|
|
||||||
sdl_ = 0 == SDL_Init(SDL_INIT_VIDEO);
|
|
||||||
if (!sdl_) {
|
|
||||||
throw Exception {"SDL init failure"};
|
|
||||||
}
|
|
||||||
SetUpGL();
|
|
||||||
SetUpWindow();
|
|
||||||
} catch (const Exception&) {
|
|
||||||
TearDown();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::Impl::Main() noexcept {
|
|
||||||
const bool alive = Update();
|
|
||||||
const auto now = clock_->now();
|
|
||||||
|
|
||||||
TaskDriver driver {logger_, now};
|
|
||||||
tasq_.Drive(driver);
|
|
||||||
|
|
||||||
if (alive_ && alive) {
|
|
||||||
concurrency_->Push(nf7::SyncTask {
|
|
||||||
now + 33ms,
|
|
||||||
[wself = weak_from_this()](auto&) {
|
|
||||||
if (auto self = wself.lock()) { self->Main(); }
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
TearDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::Impl::SetUpGL() noexcept {
|
|
||||||
# if defined(__APPLE__)
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
|
||||||
# else
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
||||||
# endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::Impl::SetUpWindow() {
|
|
||||||
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
|
||||||
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
||||||
|
|
||||||
constexpr auto flags {
|
|
||||||
SDL_WINDOW_OPENGL
|
|
||||||
| SDL_WINDOW_RESIZABLE
|
|
||||||
| SDL_WINDOW_ALLOW_HIGHDPI
|
|
||||||
};
|
|
||||||
win_ = SDL_CreateWindow(
|
|
||||||
"Dear ImGui SDL2+OpenGL3 example",
|
|
||||||
SDL_WINDOWPOS_CENTERED,
|
|
||||||
SDL_WINDOWPOS_CENTERED,
|
|
||||||
1280, 720,
|
|
||||||
flags);
|
|
||||||
if (nullptr == win_) {
|
|
||||||
throw Exception {"failed to create new window"};
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_ = SDL_GL_CreateContext(win_);
|
|
||||||
if (nullptr == gl_) {
|
|
||||||
throw Exception {"failed to create new GL context"};
|
|
||||||
}
|
|
||||||
SDL_GL_SetSwapInterval(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::Impl::TearDown() noexcept {
|
|
||||||
if (nullptr != gl_) {
|
|
||||||
SDL_GL_DeleteContext(gl_);
|
|
||||||
}
|
|
||||||
if (nullptr != win_) {
|
|
||||||
SDL_DestroyWindow(win_);
|
|
||||||
}
|
|
||||||
if (sdl_) {
|
|
||||||
SDL_Quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace nf7::core::gl3
|
} // namespace nf7::core::gl3
|
||||||
|
@ -6,10 +6,9 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "iface/subsys/clock.hh"
|
#include "iface/common/observer.hh"
|
||||||
#include "iface/subsys/concurrency.hh"
|
#include "iface/subsys/concurrency.hh"
|
||||||
#include "iface/subsys/interface.hh"
|
#include "iface/subsys/interface.hh"
|
||||||
#include "iface/subsys/logger.hh"
|
|
||||||
#include "iface/env.hh"
|
#include "iface/env.hh"
|
||||||
|
|
||||||
|
|
||||||
@ -17,19 +16,32 @@ namespace nf7::core::gl3 {
|
|||||||
|
|
||||||
class TaskContext final {
|
class TaskContext final {
|
||||||
public:
|
public:
|
||||||
TaskContext() = default;
|
TaskContext() = delete;
|
||||||
|
TaskContext(SDL_Window* win, void* gl) noexcept
|
||||||
|
: win_(win), gl_(gl) { }
|
||||||
|
|
||||||
TaskContext(const TaskContext&) = delete;
|
TaskContext(const TaskContext&) = delete;
|
||||||
TaskContext(TaskContext&&) = delete;
|
TaskContext(TaskContext&&) = delete;
|
||||||
TaskContext& operator=(const TaskContext&) = delete;
|
TaskContext& operator=(const TaskContext&) = delete;
|
||||||
TaskContext& operator=(TaskContext&&) = delete;
|
TaskContext& operator=(TaskContext&&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SDL_Window* win() const noexcept { return win_; }
|
||||||
|
void* gl() const noexcept { return gl_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Window* const win_;
|
||||||
|
void* const gl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Task = nf7::Task<TaskContext&>;
|
using Task = nf7::Task<TaskContext&>;
|
||||||
using TaskQueue = nf7::TaskQueue<Task>;
|
using TaskQueue = nf7::TaskQueue<Task>;
|
||||||
|
|
||||||
|
|
||||||
class Context : public subsys::Interface, public TaskQueue {
|
class Context :
|
||||||
|
public subsys::Interface,
|
||||||
|
public Observer<SDL_Event>::Target,
|
||||||
|
public TaskQueue {
|
||||||
public:
|
public:
|
||||||
# if defined(__APPLE__)
|
# if defined(__APPLE__)
|
||||||
static constexpr const char* kGlslVersion = "#version 150";
|
static constexpr const char* kGlslVersion = "#version 150";
|
||||||
@ -41,67 +53,15 @@ class Context : public subsys::Interface, public TaskQueue {
|
|||||||
class Impl;
|
class Impl;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Context(
|
explicit Context(Env&);
|
||||||
Env&,
|
|
||||||
const std::shared_ptr<Impl>& = nullptr);
|
|
||||||
~Context() noexcept override;
|
~Context() noexcept override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// THREAD-SAFE
|
||||||
void Push(Task&&) noexcept override;
|
void Push(Task&&) noexcept override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::shared_ptr<Impl> impl_;
|
const std::shared_ptr<Impl> impl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Context::Impl : public std::enable_shared_from_this<Impl> {
|
|
||||||
public:
|
|
||||||
class TaskDriver;
|
|
||||||
friend class Context;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Impl(Env&);
|
|
||||||
virtual ~Impl() = default;
|
|
||||||
|
|
||||||
Impl(const Impl&) = delete;
|
|
||||||
Impl(Impl&&) = delete;
|
|
||||||
Impl& operator=(const Impl&) = delete;
|
|
||||||
Impl& operator=(Impl&&) = delete;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool Update() noexcept {
|
|
||||||
SDL_Event ev;
|
|
||||||
while (SDL_PollEvent(&ev)) { }
|
|
||||||
|
|
||||||
SDL_GL_MakeCurrent(win_, gl_);
|
|
||||||
SDL_GL_SwapWindow(win_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SDL_Window* win() const noexcept { return win_; }
|
|
||||||
void* gl() const noexcept { return gl_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Exit() noexcept { alive_ = false; }
|
|
||||||
void Push(Task&& task) noexcept { tasq_.Push(std::move(task)); }
|
|
||||||
void Main() noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void SetUpGL() noexcept;
|
|
||||||
void SetUpWindow();
|
|
||||||
void TearDown() noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::shared_ptr<subsys::Clock> clock_;
|
|
||||||
const std::shared_ptr<subsys::Concurrency> concurrency_;
|
|
||||||
const std::shared_ptr<subsys::Logger> logger_;
|
|
||||||
|
|
||||||
nf7::SimpleTaskQueue<Task> tasq_;
|
|
||||||
bool alive_ {true};
|
|
||||||
|
|
||||||
bool sdl_ {false};
|
|
||||||
SDL_Window* win_ {nullptr};
|
|
||||||
void* gl_ {nullptr};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace nf7::core::gl3
|
} // namespace nf7::core::gl3
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "iface/env.hh"
|
#include "iface/env.hh"
|
||||||
|
|
||||||
#include "core/env_test.hh"
|
#include "core/env_test.hh"
|
||||||
@ -15,12 +17,11 @@ namespace nf7::core::gl3::test {
|
|||||||
class ContextFixture : public nf7::core::test::EnvFixtureWithTasking {
|
class ContextFixture : public nf7::core::test::EnvFixtureWithTasking {
|
||||||
public:
|
public:
|
||||||
ContextFixture() noexcept
|
ContextFixture() noexcept
|
||||||
: EnvFixtureWithTasking({
|
: skip_(nullptr == std::getenv("NF7_TEST_GL3")) {
|
||||||
nf7::SimpleEnv::MakePair<Context, Context>(),
|
Install<Context, Context>();
|
||||||
}),
|
}
|
||||||
skip_(nullptr == std::getenv("NF7_TEST_GL3")) { }
|
|
||||||
|
|
||||||
public:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
if (skip_) {
|
if (skip_) {
|
||||||
GTEST_SKIP();
|
GTEST_SKIP();
|
||||||
|
163
core/imgui/context.cc
Normal file
163
core/imgui/context.cc
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// No copyright
|
||||||
|
#include "core/imgui/context.hh"
|
||||||
|
|
||||||
|
#include <SDL_opengl.h>
|
||||||
|
#include <imgui_impl_opengl3.h>
|
||||||
|
#include <imgui_impl_sdl2.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "iface/common/exception.hh"
|
||||||
|
#include "iface/common/observer.hh"
|
||||||
|
#include "iface/subsys/clock.hh"
|
||||||
|
|
||||||
|
#include "core/gl3/context.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::core::imgui {
|
||||||
|
|
||||||
|
class Context::Impl final : public std::enable_shared_from_this<Impl> {
|
||||||
|
private:
|
||||||
|
static constexpr auto kUpdateInterval = std::chrono::milliseconds {33};
|
||||||
|
|
||||||
|
private:
|
||||||
|
class EventQueue final : public Observer<SDL_Event> {
|
||||||
|
public:
|
||||||
|
using Observer<SDL_Event>::Observer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventQueue(const EventQueue&) = delete;
|
||||||
|
EventQueue(EventQueue&&) = delete;
|
||||||
|
EventQueue& operator=(const EventQueue&) = delete;
|
||||||
|
EventQueue& operator=(EventQueue&&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<SDL_Event> Take() noexcept { return std::move(items_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Notify(const SDL_Event& e) noexcept override { items_.push_back(e); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<SDL_Event> items_;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Impl(Env& env)
|
||||||
|
: clock_(env.Get<subsys::Clock>()),
|
||||||
|
gl3_(env.Get<gl3::Context>()),
|
||||||
|
events_(std::make_unique<EventQueue>(*gl3_)),
|
||||||
|
imgui_(ImGui::CreateContext()) {
|
||||||
|
}
|
||||||
|
Impl(const Impl&) = delete;
|
||||||
|
Impl(Impl&&) = delete;
|
||||||
|
Impl& operator=(const Impl&) = delete;
|
||||||
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void ScheduleStart() noexcept {
|
||||||
|
gl3_->Exec([this, self = shared_from_this()](auto& t) { Start(t); });
|
||||||
|
}
|
||||||
|
void ScheduleTearDown() noexcept {
|
||||||
|
gl3_->Exec([this, self = shared_from_this()](auto& t) { TearDown(t); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<Driver>& Register(const std::shared_ptr<Driver>& driver)
|
||||||
|
try {
|
||||||
|
drivers_.emplace_back(driver);
|
||||||
|
return driver;
|
||||||
|
} catch (const std::bad_alloc&) {
|
||||||
|
throw MemoryException {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Start(gl3::TaskContext& t) noexcept {
|
||||||
|
ImGui::SetCurrentContext(imgui_);
|
||||||
|
ImGui_ImplSDL2_InitForOpenGL(t.win(), t.gl());
|
||||||
|
ImGui_ImplOpenGL3_Init(gl3::Context::kGlslVersion);
|
||||||
|
Update(t);
|
||||||
|
}
|
||||||
|
void Update(gl3::TaskContext& t) noexcept {
|
||||||
|
ImGui::SetCurrentContext(imgui_);
|
||||||
|
|
||||||
|
// get active drivers and drop dead ones
|
||||||
|
std::vector<std::shared_ptr<Driver>> drivers;
|
||||||
|
for (auto itr = drivers_.begin(); itr != drivers_.end();) {
|
||||||
|
if (auto driver = itr->lock()) {
|
||||||
|
drivers.emplace_back(std::move(driver));
|
||||||
|
++itr;
|
||||||
|
} else {
|
||||||
|
itr = drivers_.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// event handling
|
||||||
|
const auto events = events_->Take();
|
||||||
|
for (const auto& event : events) {
|
||||||
|
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// frame reset
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
// draw something
|
||||||
|
for (const auto& driver : drivers) { driver->Update(t); }
|
||||||
|
ImGui::Render();
|
||||||
|
|
||||||
|
// render them
|
||||||
|
const auto& io = ImGui::GetIO();
|
||||||
|
glViewport(0, 0,
|
||||||
|
static_cast<int>(io.DisplaySize.x),
|
||||||
|
static_cast<int>(io.DisplaySize.y));
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
for (const auto& driver : drivers) { driver->PreUpdate(t); }
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
for (const auto& driver : drivers) { driver->PostUpdate(t); }
|
||||||
|
SDL_GL_SwapWindow(t.win());
|
||||||
|
|
||||||
|
// schedule next frame
|
||||||
|
gl3_->Push(gl3::Task {
|
||||||
|
clock_->now() + kUpdateInterval,
|
||||||
|
[wself = weak_from_this()](auto& t) {
|
||||||
|
if (auto self = wself.lock()) { self->Update(t); }
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void TearDown(gl3::TaskContext&) noexcept {
|
||||||
|
ImGui::SetCurrentContext(imgui_);
|
||||||
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext(imgui_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::shared_ptr<subsys::Clock> clock_;
|
||||||
|
const std::shared_ptr<gl3::Context> gl3_;
|
||||||
|
const std::unique_ptr<EventQueue> events_;
|
||||||
|
|
||||||
|
ImGuiContext* const imgui_;
|
||||||
|
|
||||||
|
std::vector<std::weak_ptr<Driver>> drivers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
Context::Context(Env& env)
|
||||||
|
try : subsys::Interface("nf7::core::imgui::Context"),
|
||||||
|
impl_(std::make_shared<Impl>(env)) {
|
||||||
|
impl_->ScheduleStart();
|
||||||
|
} catch (const std::bad_alloc&) {
|
||||||
|
throw MemoryException {};
|
||||||
|
}
|
||||||
|
Context::~Context() noexcept {
|
||||||
|
impl_->ScheduleTearDown();
|
||||||
|
}
|
||||||
|
const std::shared_ptr<Driver>& Context::Register(
|
||||||
|
const std::shared_ptr<Driver>& driver) {
|
||||||
|
return impl_->Register(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7::core::imgui
|
31
core/imgui/context.hh
Normal file
31
core/imgui/context.hh
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "iface/env.hh"
|
||||||
|
|
||||||
|
#include "core/imgui/driver.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::core::imgui {
|
||||||
|
|
||||||
|
class Context : public subsys::Interface {
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Context(Env&);
|
||||||
|
~Context() noexcept override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const std::shared_ptr<Driver>& Register(
|
||||||
|
const std::shared_ptr<Driver>& driver);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::shared_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7::core::imgui
|
38
core/imgui/context_test.cc
Normal file
38
core/imgui/context_test.cc
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// No copyright
|
||||||
|
#include "core/imgui/context.hh"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "core/imgui/context_test.hh"
|
||||||
|
#include "core/imgui/driver_test.hh"
|
||||||
|
|
||||||
|
|
||||||
|
// adds meaningless suffix to avoid a conflict with ImGuiContext
|
||||||
|
using ImGuiContext0 = nf7::core::imgui::test::ContextFixture;
|
||||||
|
|
||||||
|
TEST_F(ImGuiContext0, Init) {
|
||||||
|
const auto clock = env().Get<nf7::subsys::Clock>();
|
||||||
|
const auto concurrency = env().Get<nf7::subsys::Concurrency>();
|
||||||
|
|
||||||
|
auto ctx = env().Get<nf7::core::imgui::Context>();
|
||||||
|
concurrency->Push(nf7::SyncTask {
|
||||||
|
clock->now() + std::chrono::seconds {10},
|
||||||
|
[&](auto&) { ctx = nullptr; },
|
||||||
|
});
|
||||||
|
|
||||||
|
auto driver = std::make_shared<
|
||||||
|
::testing::NiceMock<nf7::core::imgui::test::DriverMock>>();
|
||||||
|
ctx->Register(driver);
|
||||||
|
|
||||||
|
bool shown = true;
|
||||||
|
ON_CALL(*driver, Update).WillByDefault([&](auto&) {
|
||||||
|
ImGui::ShowDemoWindow(&shown);
|
||||||
|
});
|
||||||
|
|
||||||
|
DropEnv();
|
||||||
|
ConsumeTasks();
|
||||||
|
}
|
16
core/imgui/context_test.hh
Normal file
16
core/imgui/context_test.hh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "core/gl3/context_test.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::core::imgui::test {
|
||||||
|
|
||||||
|
class ContextFixture : public gl3::test::ContextFixture {
|
||||||
|
public:
|
||||||
|
ContextFixture() { Install<Context, Context>(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7::core::imgui::test
|
25
core/imgui/driver.hh
Normal file
25
core/imgui/driver.hh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/gl3/context.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::core::imgui {
|
||||||
|
|
||||||
|
class Driver {
|
||||||
|
public:
|
||||||
|
Driver() = default;
|
||||||
|
virtual ~Driver() = default;
|
||||||
|
|
||||||
|
Driver(const Driver&) = delete;
|
||||||
|
Driver(Driver&&) = delete;
|
||||||
|
Driver& operator=(const Driver&) = delete;
|
||||||
|
Driver& operator=(Driver&&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void PreUpdate(gl3::TaskContext&) noexcept { }
|
||||||
|
virtual void Update(gl3::TaskContext&) noexcept { }
|
||||||
|
virtual void PostUpdate(gl3::TaskContext&) noexcept { }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7::core::imgui
|
22
core/imgui/driver_test.hh
Normal file
22
core/imgui/driver_test.hh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// No copyright
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/imgui/driver.hh"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include "core/gl3/context.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::core::imgui::test {
|
||||||
|
|
||||||
|
class DriverMock : public Driver {
|
||||||
|
public:
|
||||||
|
using Driver::Driver;
|
||||||
|
|
||||||
|
MOCK_METHOD(void, PreUpdate, (gl3::TaskContext&), (noexcept, override));
|
||||||
|
MOCK_METHOD(void, Update, (gl3::TaskContext&), (noexcept, override));
|
||||||
|
MOCK_METHOD(void, PostUpdate, (gl3::TaskContext&), (noexcept, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7::core::imgui::test
|
@ -159,7 +159,7 @@ class AsyncContext final :
|
|||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::shared_ptr<Context> Context::Create(Env& env, Kind kind) {
|
std::shared_ptr<Context> Context::Make(Env& env, Kind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case kSync:
|
case kSync:
|
||||||
return std::make_shared<SyncContext>(env);
|
return std::make_shared<SyncContext>(env);
|
||||||
|
@ -147,7 +147,13 @@ class Context :
|
|||||||
kSync,
|
kSync,
|
||||||
kAsync,
|
kAsync,
|
||||||
};
|
};
|
||||||
static std::shared_ptr<Context> Create(Env&, Kind);
|
static std::shared_ptr<Context> Make(Env&, Kind);
|
||||||
|
static std::shared_ptr<Context> MakeAsync(Env& env) {
|
||||||
|
return Make(env, kAsync);
|
||||||
|
}
|
||||||
|
static std::shared_ptr<Context> MakeSync(Env& env) {
|
||||||
|
return Make(env, kSync);
|
||||||
|
}
|
||||||
|
|
||||||
explicit Context(const char* name, Kind kind)
|
explicit Context(const char* name, Kind kind)
|
||||||
: subsys::Interface(name), kind_(kind), state_(nullptr) {
|
: subsys::Interface(name), kind_(kind), state_(nullptr) {
|
||||||
|
@ -26,14 +26,9 @@ class ContextFixture :
|
|||||||
public nf7::core::test::EnvFixtureWithTasking,
|
public nf7::core::test::EnvFixtureWithTasking,
|
||||||
public ::testing::WithParamInterface<Context::Kind> {
|
public ::testing::WithParamInterface<Context::Kind> {
|
||||||
public:
|
public:
|
||||||
ContextFixture() noexcept
|
ContextFixture() {
|
||||||
: EnvFixtureWithTasking({
|
Install<Context>([](auto& env) { return Context::Make(env, GetParam()); });
|
||||||
{
|
}
|
||||||
typeid(Context), [](auto& env) {
|
|
||||||
return Context::Create(env, GetParam());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}) { }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nf7::core::luajit::test
|
} // namespace nf7::core::luajit::test
|
||||||
|
@ -15,12 +15,9 @@ namespace nf7::core::sqlite::test {
|
|||||||
|
|
||||||
class DatabaseFixture : public nf7::core::test::EnvFixtureWithTasking {
|
class DatabaseFixture : public nf7::core::test::EnvFixtureWithTasking {
|
||||||
public:
|
public:
|
||||||
DatabaseFixture()
|
DatabaseFixture() {
|
||||||
: nf7::core::test::EnvFixtureWithTasking({
|
Install<subsys::Database>(
|
||||||
{typeid(nf7::subsys::Database), [](auto& env) {
|
[](auto& env) { return std::make_shared<Database>(env, ":memory:"); });
|
||||||
return std::make_shared<Database>(env, ":memory:");
|
|
||||||
}},
|
|
||||||
}) {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,11 +20,9 @@ namespace nf7::core::uv::test {
|
|||||||
|
|
||||||
class ContextFixture : public nf7::core::test::EnvFixture {
|
class ContextFixture : public nf7::core::test::EnvFixture {
|
||||||
public:
|
public:
|
||||||
ContextFixture() noexcept
|
ContextFixture() {
|
||||||
: nf7::core::test::EnvFixture({
|
Install<Context, MainContext>();
|
||||||
SimpleEnv::MakePair<Context, MainContext>(),
|
Install<subsys::Clock, Clock>();
|
||||||
SimpleEnv::MakePair<subsys::Clock, Clock>(),
|
|
||||||
}) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
4
thirdparty/CMakeLists.txt
vendored
4
thirdparty/CMakeLists.txt
vendored
@ -29,6 +29,10 @@ FetchContent_Declare(
|
|||||||
FetchContent_Populate(luajit)
|
FetchContent_Populate(luajit)
|
||||||
include(luajit.cmake)
|
include(luajit.cmake)
|
||||||
|
|
||||||
|
# ---- OpenGL
|
||||||
|
find_package(OpenGL REQUIRED GLOBAL)
|
||||||
|
add_library(OpenGL ALIAS OpenGL::GL)
|
||||||
|
|
||||||
# ---- SDL2 (zlib)
|
# ---- SDL2 (zlib)
|
||||||
find_package(SDL2 REQUIRED GLOBAL)
|
find_package(SDL2 REQUIRED GLOBAL)
|
||||||
add_library(SDL2 ALIAS SDL2::SDL2)
|
add_library(SDL2 ALIAS SDL2::SDL2)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user