Compare commits
33 Commits
589cd4b4fc
...
615d2eacb0
Author | SHA1 | Date | |
---|---|---|---|
615d2eacb0 | |||
c2c4b83918 | |||
4f13dd9456 | |||
7b2f9c8d55 | |||
b949383932 | |||
5ef347fa2e | |||
f77a60831c | |||
8688ef98b6 | |||
d0d6a2ebd5 | |||
ab802d02e3 | |||
007882ccfd | |||
2082a6e482 | |||
8ffad3347f | |||
7275e9a710 | |||
46e6a78682 | |||
09375ced9c | |||
79f3cc9639 | |||
f16937da5a | |||
7dbda8d281 | |||
8879e9ed41 | |||
e045e86b11 | |||
07b198f71e | |||
b004723464 | |||
6e820daef8 | |||
dcbd3594cf | |||
c5590092fa | |||
8339cc814a | |||
bbfee304bd | |||
2040898bd7 | |||
46ddb16128 | |||
d6a9c62a63 | |||
a5f3e459bf | |||
336f436942 |
@ -5,13 +5,9 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
|||||||
project(nf7 C CXX)
|
project(nf7 C CXX)
|
||||||
|
|
||||||
option(NF7_STATIC "link all libs statically" ON)
|
option(NF7_STATIC "link all libs statically" ON)
|
||||||
|
option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
|
||||||
|
|
||||||
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
|
set(NF7_OPTIONS_WARNING
|
||||||
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(NF7_CXX_FLAGS
|
|
||||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
||||||
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
|
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
|
||||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
|
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
|
||||||
@ -19,6 +15,23 @@ set(NF7_CXX_FLAGS
|
|||||||
$<$<CXX_COMPILER_ID:MSVC>:
|
$<$<CXX_COMPILER_ID:MSVC>:
|
||||||
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
|
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
|
||||||
)
|
)
|
||||||
|
if (NF7_SANITIZE_THREAD)
|
||||||
|
set(NF7_OPTIONS_SANITIZE
|
||||||
|
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
|
||||||
|
-fsanitize=thread -fno-omit-frame-pointer>>
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set(NF7_OPTIONS_SANITIZE
|
||||||
|
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
|
||||||
|
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
|
||||||
|
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
@ -27,9 +40,14 @@ add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
|||||||
|
|
||||||
# ---- application ----
|
# ---- application ----
|
||||||
add_executable(nf7)
|
add_executable(nf7)
|
||||||
target_compile_options(nf7 PRIVATE ${NF7_CXX_FLAGS})
|
|
||||||
target_include_directories(nf7 PRIVATE . "${PROJECT_BINARY_DIR}/include")
|
target_include_directories(nf7 PRIVATE . "${PROJECT_BINARY_DIR}/include")
|
||||||
|
target_compile_options(nf7 PRIVATE
|
||||||
|
${NF7_OPTIONS_WARNING}
|
||||||
|
${NF7_OPTIONS_SANITIZE}
|
||||||
|
)
|
||||||
|
target_link_options(nf7 PRIVATE
|
||||||
|
${NF7_OPTIONS_SANITIZE}
|
||||||
|
)
|
||||||
target_compile_definitions(nf7
|
target_compile_definitions(nf7
|
||||||
PRIVATE
|
PRIVATE
|
||||||
IMGUI_DEFINE_MATH_OPERATORS
|
IMGUI_DEFINE_MATH_OPERATORS
|
||||||
@ -59,6 +77,7 @@ target_sources(nf7
|
|||||||
common/generic_type_info.hh
|
common/generic_type_info.hh
|
||||||
common/generic_watcher.hh
|
common/generic_watcher.hh
|
||||||
common/gui_dnd.hh
|
common/gui_dnd.hh
|
||||||
|
common/gui_config.hh
|
||||||
common/gui_context.hh
|
common/gui_context.hh
|
||||||
common/gui_file.hh
|
common/gui_file.hh
|
||||||
common/gui_file.cc
|
common/gui_file.cc
|
||||||
@ -83,20 +102,25 @@ target_sources(nf7
|
|||||||
common/memento.hh
|
common/memento.hh
|
||||||
common/memento_recorder.hh
|
common/memento_recorder.hh
|
||||||
common/mutable_memento.hh
|
common/mutable_memento.hh
|
||||||
common/native_file.hh
|
common/mutex.hh
|
||||||
|
common/nfile.hh
|
||||||
|
common/nfile_watcher.hh
|
||||||
common/node.hh
|
common/node.hh
|
||||||
common/node_link_store.hh
|
common/node_link_store.hh
|
||||||
common/node_root_lambda.hh
|
common/node_root_lambda.hh
|
||||||
common/node_root_select_lambda.hh
|
common/node_root_select_lambda.hh
|
||||||
common/ptr_selector.hh
|
common/ptr_selector.hh
|
||||||
common/queue.hh
|
common/queue.hh
|
||||||
|
common/ring_buffer.hh
|
||||||
common/sequencer.hh
|
common/sequencer.hh
|
||||||
common/squashed_history.hh
|
common/squashed_history.hh
|
||||||
|
common/task.hh
|
||||||
common/thread.hh
|
common/thread.hh
|
||||||
common/timed_queue.hh
|
common/timed_queue.hh
|
||||||
|
common/util_algorithm.hh
|
||||||
common/util_string.hh
|
common/util_string.hh
|
||||||
common/value.hh
|
common/value.hh
|
||||||
common/yas_audio.hh
|
common/yas_enum.hh
|
||||||
common/yas_imgui.hh
|
common/yas_imgui.hh
|
||||||
common/yas_imnodes.hh
|
common/yas_imnodes.hh
|
||||||
common/yas_nf7.hh
|
common/yas_nf7.hh
|
||||||
@ -104,8 +128,8 @@ target_sources(nf7
|
|||||||
common/yas_std_filesystem.hh
|
common/yas_std_filesystem.hh
|
||||||
common/yas_std_variant.hh
|
common/yas_std_variant.hh
|
||||||
|
|
||||||
$<$<PLATFORM_ID:Linux>:common/native_file_unix.cc>
|
$<$<PLATFORM_ID:Linux>:common/nfile_unix.cc>
|
||||||
$<$<PLATFORM_ID:Windows>:common/native_file_win.cc>
|
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
|
||||||
|
|
||||||
file/audio_context.cc
|
file/audio_context.cc
|
||||||
file/audio_device.cc
|
file/audio_device.cc
|
||||||
@ -123,8 +147,9 @@ target_sources(nf7
|
|||||||
file/system_event.cc
|
file/system_event.cc
|
||||||
file/system_imgui.cc
|
file/system_imgui.cc
|
||||||
file/system_logger.cc
|
file/system_logger.cc
|
||||||
file/system_native_file.cc
|
file/system_nfile.cc
|
||||||
file/value_curve.cc
|
file/value_curve.cc
|
||||||
|
file/value_plot.cc
|
||||||
)
|
)
|
||||||
target_link_libraries(nf7
|
target_link_libraries(nf7
|
||||||
PRIVATE
|
PRIVATE
|
||||||
@ -135,7 +160,9 @@ target_link_libraries(nf7
|
|||||||
implot
|
implot
|
||||||
linalg.h
|
linalg.h
|
||||||
luajit
|
luajit
|
||||||
|
magic_enum
|
||||||
miniaudio
|
miniaudio
|
||||||
source_location
|
source_location
|
||||||
yas
|
yas
|
||||||
|
yaml-cpp
|
||||||
)
|
)
|
||||||
|
106
common/future.hh
106
common/future.hh
@ -24,16 +24,22 @@ namespace nf7 {
|
|||||||
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
|
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
|
||||||
//
|
//
|
||||||
// Users who receive Future can wait for finishing
|
// Users who receive Future can wait for finishing
|
||||||
// by Future::Then(), Future::ThenSub(), or co_await.
|
// by Future::Then(), Future::ThenIf(), Future::Catch(), or co_await.
|
||||||
|
|
||||||
|
|
||||||
|
class CoroutineAbortException final : public nf7::Exception {
|
||||||
|
public:
|
||||||
|
using nf7::Exception::Exception;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// T must not be void, use std::monostate instead
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class Future final {
|
class Future final {
|
||||||
public:
|
public:
|
||||||
class Promise;
|
class Promise;
|
||||||
class Coro;
|
class Coro;
|
||||||
|
|
||||||
|
using ThisFuture = nf7::Future<T>;
|
||||||
using Handle = std::coroutine_handle<Promise>;
|
using Handle = std::coroutine_handle<Promise>;
|
||||||
using Imm = std::variant<T, std::exception_ptr>;
|
using Imm = std::variant<T, std::exception_ptr>;
|
||||||
|
|
||||||
@ -59,7 +65,7 @@ class Future final {
|
|||||||
// Factory side have this to tell finish or abort.
|
// Factory side have this to tell finish or abort.
|
||||||
class Promise final {
|
class Promise final {
|
||||||
public:
|
public:
|
||||||
// Use data_() instead, MSVC doesn't allow this:
|
// Use data_() instead, MSVC can't understand the followings:
|
||||||
// template <typename U> friend class nf7::Future<U>;
|
// template <typename U> friend class nf7::Future<U>;
|
||||||
// template <typename U> friend class nf7::Future<U>::Coro;
|
// template <typename U> friend class nf7::Future<U>::Coro;
|
||||||
|
|
||||||
@ -90,11 +96,14 @@ class Future final {
|
|||||||
auto Return(T&& v) noexcept {
|
auto Return(T&& v) noexcept {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
if (data_->state == kYet) {
|
if (data_->state == kYet) {
|
||||||
data_->state = kDone;
|
|
||||||
data_->value = std::move(v);
|
data_->value = std::move(v);
|
||||||
|
data_->state = kDone;
|
||||||
CallReceivers();
|
CallReceivers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto Return(const T& v) noexcept {
|
||||||
|
Return(T {v});
|
||||||
|
}
|
||||||
// thread-safe
|
// thread-safe
|
||||||
void Throw(std::exception_ptr e) noexcept {
|
void Throw(std::exception_ptr e) noexcept {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
@ -104,21 +113,25 @@ class Future final {
|
|||||||
CallReceivers();
|
CallReceivers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
template <typename E, typename... Args>
|
||||||
|
void Throw(Args&&... args) noexcept {
|
||||||
|
return Throw(std::make_exception_ptr<E>(E {std::forward<Args>(args)...}));
|
||||||
|
}
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
// Do Return(f()) if no exception is thrown, otherwise call Throw().
|
// Do Return(f()) if no exception is thrown, otherwise call Throw().
|
||||||
auto Wrap(const std::function<T()>& f) noexcept
|
auto Wrap(const std::function<T()>& f) noexcept
|
||||||
try {
|
try {
|
||||||
Return(f());
|
Return(f());
|
||||||
} catch (Exception&) {
|
} catch (...) {
|
||||||
Throw(std::current_exception());
|
Throw(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
// Creates Future() object.
|
// Creates Future() object.
|
||||||
Future future() const noexcept {
|
ThisFuture future() const noexcept {
|
||||||
assert(data_);
|
assert(data_);
|
||||||
return Future(data_);
|
return ThisFuture(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get_return_object() noexcept {
|
auto get_return_object() noexcept {
|
||||||
@ -179,14 +192,14 @@ class Future final {
|
|||||||
Coro& operator=(const Coro&) = delete;
|
Coro& operator=(const Coro&) = delete;
|
||||||
Coro& operator=(Coro&&) = default;
|
Coro& operator=(Coro&&) = default;
|
||||||
|
|
||||||
Future Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
|
ThisFuture Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
|
||||||
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
|
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
|
||||||
data_->ctx = ctx;
|
data_->ctx = ctx;
|
||||||
return Future(data_);
|
return ThisFuture(data_);
|
||||||
}
|
}
|
||||||
void Abort() noexcept {
|
void Abort() noexcept {
|
||||||
h_.promise().Throw(
|
h_.promise().Throw(
|
||||||
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
|
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
|
||||||
data_->aborted = true;
|
data_->aborted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,43 +221,75 @@ class Future final {
|
|||||||
}
|
}
|
||||||
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
|
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
|
||||||
}
|
}
|
||||||
Future(const Future&) = default;
|
Future(const ThisFuture&) = default;
|
||||||
Future(Future&&) = default;
|
Future(ThisFuture&&) = default;
|
||||||
Future& operator=(const Future&) = default;
|
Future& operator=(const ThisFuture&) = default;
|
||||||
Future& operator=(Future&&) = default;
|
Future& operator=(ThisFuture&&) = default;
|
||||||
|
|
||||||
// Schedules to execute f() immediately on any thread
|
// Schedules to execute f() immediately on any thread
|
||||||
// when the promise is finished or aborted.
|
// when the promise is finished or aborted.
|
||||||
Future& Then(std::function<void(Future)>&& f) noexcept {
|
// If ctx is not nullptr, the function will be run synchronized with main thread.
|
||||||
|
ThisFuture& Then(const std::shared_ptr<nf7::Context>& ctx, std::function<void(const ThisFuture&)>&& f) noexcept {
|
||||||
|
auto fun = std::move(f);
|
||||||
|
if (ctx) {
|
||||||
|
fun = [ctx, fun = std::move(fun)](auto& fu) {
|
||||||
|
ctx->env().ExecSub(
|
||||||
|
ctx, [fu, fun = std::move(fun)]() mutable { fun(fu); });
|
||||||
|
};
|
||||||
|
}
|
||||||
if (data_) {
|
if (data_) {
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
std::unique_lock<std::mutex> k(data_->mtx);
|
||||||
if (yet()) {
|
if (yet()) {
|
||||||
data_->recv.push_back(
|
data_->recv.push_back(
|
||||||
[d = data_, f = std::move(f)]() { f(Future(d)); });
|
[fun = std::move(fun), d = data_]() mutable { fun(ThisFuture {d}); });
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f(*this);
|
fun(*this);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
template <typename R>
|
||||||
|
nf7::Future<R> Then(const std::shared_ptr<nf7::Context>& ctx,
|
||||||
|
std::function<void(const ThisFuture&, typename nf7::Future<R>::Promise&)>&& f) noexcept {
|
||||||
|
typename nf7::Future<R>::Promise pro;
|
||||||
|
Then(ctx, [pro, f = std::move(f)](auto& fu) mutable {
|
||||||
|
try {
|
||||||
|
f(fu, pro);
|
||||||
|
} catch (...) {
|
||||||
|
pro.Throw(std::current_exception());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return pro.future();
|
||||||
|
}
|
||||||
|
ThisFuture& Then(auto&& f) noexcept {
|
||||||
|
return Then(nullptr, std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
// Schedules to execute f() as a sub task when the promise is finished or aborted.
|
// same as Then() but called when it's done without error
|
||||||
Future& ThenSub(const std::shared_ptr<nf7::Context>& ctx,
|
ThisFuture& ThenIf(const std::shared_ptr<nf7::Context>& ctx, std::function<void(const T&)>&& f) noexcept {
|
||||||
std::function<void(Future)>&& f) noexcept {
|
Then(ctx, [f = std::move(f)](auto& fu) {
|
||||||
if (data_) {
|
if (fu.done()) f(fu.value());
|
||||||
std::unique_lock<std::mutex> k(data_->mtx);
|
|
||||||
if (yet()) {
|
|
||||||
data_->recv.push_back([d = data_, ctx, f = std::move(f)]() {
|
|
||||||
ctx->env().ExecSub(ctx, std::bind(f, Future(d)));
|
|
||||||
});
|
});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
}
|
ThisFuture& ThenIf(auto&& f) noexcept {
|
||||||
ctx->env().ExecSub(ctx, std::bind(f, *this));
|
return ThenIf(nullptr, std::move(f));
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& value() {
|
// same as Then() but called when it caused an exception
|
||||||
|
template <typename E>
|
||||||
|
ThisFuture& Catch(const std::shared_ptr<nf7::Context>& ctx, std::function<void(E&)>&& f) noexcept {
|
||||||
|
Then(ctx, [f = std::move(f)](auto& fu) {
|
||||||
|
try { fu.value(); } catch (E& e) { f(e); } catch (...) { }
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template <typename E>
|
||||||
|
ThisFuture& Catch(auto&& f) noexcept {
|
||||||
|
return Catch<E>(nullptr, std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& value() const {
|
||||||
if (imm_) {
|
if (imm_) {
|
||||||
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
|
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
|
||||||
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
|
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
|
||||||
@ -283,7 +328,6 @@ class Future final {
|
|||||||
|
|
||||||
std::unique_lock<std::mutex> k(data.mtx);
|
std::unique_lock<std::mutex> k(data.mtx);
|
||||||
auto callee_ctx = data.ctx.lock();
|
auto callee_ctx = data.ctx.lock();
|
||||||
assert(callee_ctx);
|
|
||||||
|
|
||||||
auto caller_data = caller.promise().data__();
|
auto caller_data = caller.promise().data__();
|
||||||
auto caller_ctx = caller_data->ctx.lock();
|
auto caller_ctx = caller_data->ctx.lock();
|
||||||
@ -306,7 +350,7 @@ class Future final {
|
|||||||
caller.resume();
|
caller.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto await_resume() { return value(); }
|
auto& await_resume() { return value(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<Imm> imm_;
|
std::optional<Imm> imm_;
|
||||||
|
@ -29,6 +29,13 @@ class GenericMemento : public nf7::MutableMemento {
|
|||||||
assert(map_.empty());
|
assert(map_.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T* operator->() noexcept {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
const T* operator->() const noexcept {
|
||||||
|
return &data_;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Tag> Save() noexcept override {
|
std::shared_ptr<Tag> Save() noexcept override {
|
||||||
if (tag_) return tag_;
|
if (tag_) return tag_;
|
||||||
auto [itr, emplaced] = map_.emplace(next_++, data_);
|
auto [itr, emplaced] = map_.emplace(next_++, data_);
|
||||||
|
59
common/gui_config.hh
Normal file
59
common/gui_config.hh
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_stdlib.h>
|
||||||
|
|
||||||
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/generic_memento.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::gui {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept ConfigData = requires (T& x) {
|
||||||
|
{ x.Stringify() } -> std::convertible_to<std::string>;
|
||||||
|
x.Parse(std::string {});
|
||||||
|
};
|
||||||
|
|
||||||
|
template <ConfigData T>
|
||||||
|
void Config(nf7::GenericMemento<T>& mem) noexcept {
|
||||||
|
static std::string text_;
|
||||||
|
static std::string msg_;
|
||||||
|
static bool mod_;
|
||||||
|
|
||||||
|
if (ImGui::IsWindowAppearing()) {
|
||||||
|
text_ = mem->Stringify();
|
||||||
|
msg_ = "";
|
||||||
|
mod_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_ |= ImGui::InputTextMultiline("##config", &text_);
|
||||||
|
|
||||||
|
ImGui::BeginDisabled(!mod_);
|
||||||
|
if (ImGui::Button("apply")) {
|
||||||
|
try {
|
||||||
|
mem->Parse(text_);
|
||||||
|
mem.Commit();
|
||||||
|
msg_ = "";
|
||||||
|
mod_ = false;
|
||||||
|
} catch (nf7::Exception& e) {
|
||||||
|
msg_ = e.msg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("restore")) {
|
||||||
|
text_ = mem->Stringify();
|
||||||
|
msg_ = "";
|
||||||
|
mod_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg_.size()) {
|
||||||
|
ImGui::Bullet();
|
||||||
|
ImGui::TextUnformatted(msg_.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7::gui
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/util_algorithm.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nf7::gui {
|
namespace nf7::gui {
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ class Popup {
|
|||||||
return ImGui::BeginPopup(name_, flags_);
|
return ImGui::BeginPopup(name_, flags_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* name() const noexcept { return name_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char* name_;
|
const char* name_;
|
||||||
ImGuiWindowFlags flags_;
|
ImGuiWindowFlags flags_;
|
||||||
@ -36,6 +38,7 @@ class Popup {
|
|||||||
std::optional<ImGuiPopupFlags> open_flags_;
|
std::optional<ImGuiPopupFlags> open_flags_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class IOSocketListPopup final :
|
class IOSocketListPopup final :
|
||||||
public nf7::FileBase::Feature, private Popup {
|
public nf7::FileBase::Feature, private Popup {
|
||||||
public:
|
public:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -8,11 +9,6 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class LifeExpiredException final : public nf7::Exception {
|
|
||||||
public:
|
|
||||||
using nf7::Exception::Exception;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class Life final {
|
class Life final {
|
||||||
public:
|
public:
|
||||||
@ -33,7 +29,7 @@ class Life final {
|
|||||||
T* const ptr_;
|
T* const ptr_;
|
||||||
|
|
||||||
struct Data final {
|
struct Data final {
|
||||||
T* ptr;
|
std::atomic<T*> ptr;
|
||||||
};
|
};
|
||||||
std::shared_ptr<Data> data_;
|
std::shared_ptr<Data> data_;
|
||||||
};
|
};
|
||||||
@ -57,7 +53,7 @@ class Life<T>::Ref final {
|
|||||||
|
|
||||||
void EnforceAlive() const {
|
void EnforceAlive() const {
|
||||||
if (!data_->ptr) {
|
if (!data_->ptr) {
|
||||||
throw LifeExpiredException {"target expired"};
|
throw nf7::ExpiredException {"target expired"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
163
common/luajit.cc
163
common/luajit.cc
@ -17,11 +17,7 @@
|
|||||||
|
|
||||||
namespace nf7::luajit {
|
namespace nf7::luajit {
|
||||||
|
|
||||||
// pushes original libraries
|
static void PushStd(lua_State* L) noexcept;
|
||||||
static void PushLuaLib(lua_State* L) noexcept;
|
|
||||||
static void PushMathLib(lua_State* L) noexcept;
|
|
||||||
static void PushTableLib(lua_State* L) noexcept;
|
|
||||||
static void PushTimeLib(lua_State* L) noexcept;
|
|
||||||
|
|
||||||
// buffer <-> lua value conversion
|
// buffer <-> lua value conversion
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -35,62 +31,8 @@ static size_t ToBytes(lua_State* L, uint8_t* ptr, uint8_t* end);
|
|||||||
|
|
||||||
void PushGlobalTable(lua_State* L) noexcept {
|
void PushGlobalTable(lua_State* L) noexcept {
|
||||||
if (luaL_newmetatable(L, "nf7::luajit::PushGlobalTable")) {
|
if (luaL_newmetatable(L, "nf7::luajit::PushGlobalTable")) {
|
||||||
PushLuaLib(L);
|
PushStd(L);
|
||||||
lua_setfield(L, -2, "lua");
|
lua_setfield(L, -2, "std");
|
||||||
|
|
||||||
PushMathLib(L);
|
|
||||||
lua_setfield(L, -2, "math");
|
|
||||||
|
|
||||||
PushTableLib(L);
|
|
||||||
lua_setfield(L, -2, "table");
|
|
||||||
|
|
||||||
PushTimeLib(L);
|
|
||||||
lua_setfield(L, -2, "time");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
|
||||||
if (lua_isstring(L, 2)) {
|
|
||||||
const char* type = lua_tostring(L, 2);
|
|
||||||
if (std::string_view {"integer"} == type) {
|
|
||||||
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
|
|
||||||
} else {
|
|
||||||
return luaL_error(L, "unknown type specifier: %s", type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PushValue(L, CheckValue(L, 1));
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
});
|
|
||||||
lua_setfield(L, -2, "nf7_Value");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
|
||||||
if (auto imm = ToVector(L, 1)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (auto mut = ToMutableVector(L, 1)) {
|
|
||||||
PushVector(L, std::make_shared<std::vector<uint8_t>>(std::move(*mut)));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return luaL_error(L, "expected nf7::Value::MutableVector or nf7::Value::ConstVector");
|
|
||||||
});
|
|
||||||
lua_setfield(L, -2, "nf7_Vector");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, [](auto L) {
|
|
||||||
if (auto imm = ToVector(L, 1)) {
|
|
||||||
if (imm->use_count() == 1) {
|
|
||||||
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
|
|
||||||
} else {
|
|
||||||
PushMutableVector(L, std::vector<uint8_t> {**imm});
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (auto mut = ToMutableVector(L, 1)) {
|
|
||||||
PushMutableVector(L, std::vector<uint8_t> {*mut});
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
PushMutableVector(L, {});
|
|
||||||
return 1;
|
|
||||||
});
|
|
||||||
lua_setfield(L, -2, "nf7_MutableVector");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void PushImmEnv(lua_State* L) noexcept {
|
void PushImmEnv(lua_State* L) noexcept {
|
||||||
@ -439,12 +381,14 @@ std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void PushLuaLib(lua_State* L) noexcept {
|
static void PushStd(lua_State* L) noexcept {
|
||||||
lua_newuserdata(L, 0);
|
lua_newuserdata(L, 0);
|
||||||
|
|
||||||
lua_createtable(L, 0, 0);
|
lua_createtable(L, 0, 0);
|
||||||
lua_createtable(L, 0, 0);
|
lua_createtable(L, 0, 0);
|
||||||
{
|
{
|
||||||
|
// ---- lua lib ----
|
||||||
|
|
||||||
|
// assert(expr[, msg])
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
if (lua_toboolean(L, 1)) {
|
if (lua_toboolean(L, 1)) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -457,11 +401,13 @@ static void PushLuaLib(lua_State* L) noexcept {
|
|||||||
});
|
});
|
||||||
lua_setfield(L, -2, "assert");
|
lua_setfield(L, -2, "assert");
|
||||||
|
|
||||||
|
// error(msg)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
return luaL_error(L, luaL_checkstring(L, 1));
|
return luaL_error(L, luaL_checkstring(L, 1));
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "error");
|
lua_setfield(L, -2, "error");
|
||||||
|
|
||||||
|
// load(str)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
if (0 != luaL_loadstring(L, luaL_checkstring(L, 1))) {
|
if (0 != luaL_loadstring(L, luaL_checkstring(L, 1))) {
|
||||||
return luaL_error(L, "lua.load error: %s", lua_tostring(L, -1));
|
return luaL_error(L, "lua.load error: %s", lua_tostring(L, -1));
|
||||||
@ -470,6 +416,7 @@ static void PushLuaLib(lua_State* L) noexcept {
|
|||||||
});
|
});
|
||||||
lua_setfield(L, -2, "load");
|
lua_setfield(L, -2, "load");
|
||||||
|
|
||||||
|
// pcall(func, args...) -> success, result
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
|
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
|
||||||
lua_pushboolean(L, true);
|
lua_pushboolean(L, true);
|
||||||
@ -482,44 +429,35 @@ static void PushLuaLib(lua_State* L) noexcept {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "pcall");
|
lua_setfield(L, -2, "pcall");
|
||||||
}
|
|
||||||
lua_setfield(L, -2, "__index");
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
}
|
|
||||||
static void PushMathLib(lua_State* L) noexcept {
|
|
||||||
lua_newuserdata(L, 0);
|
|
||||||
|
|
||||||
lua_createtable(L, 0, 0);
|
|
||||||
lua_createtable(L, 0, 0);
|
// ---- math lib ----
|
||||||
{
|
|
||||||
|
// sin(theta)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
|
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "sin");
|
lua_setfield(L, -2, "sin");
|
||||||
|
|
||||||
|
// cos(theta)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
|
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "cos");
|
lua_setfield(L, -2, "cos");
|
||||||
|
|
||||||
|
// tan(slope)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
|
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "tan");
|
lua_setfield(L, -2, "tan");
|
||||||
}
|
|
||||||
lua_setfield(L, -2, "__index");
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
}
|
|
||||||
static void PushTableLib(lua_State* L) noexcept {
|
|
||||||
lua_newuserdata(L, 0);
|
|
||||||
|
|
||||||
lua_createtable(L, 0, 0);
|
|
||||||
lua_createtable(L, 0, 0);
|
// ---- table lib ----
|
||||||
{
|
|
||||||
// table.setmetatable(table, meta_table)
|
// meta(table, meta_table)
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
luaL_checktype(L, 1, LUA_TTABLE);
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
luaL_checktype(L, 2, LUA_TTABLE);
|
luaL_checktype(L, 2, LUA_TTABLE);
|
||||||
@ -527,18 +465,12 @@ static void PushTableLib(lua_State* L) noexcept {
|
|||||||
lua_setmetatable(L, 1);
|
lua_setmetatable(L, 1);
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "setmetatable");
|
lua_setfield(L, -2, "meta");
|
||||||
}
|
|
||||||
lua_setfield(L, -2, "__index");
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
}
|
|
||||||
static void PushTimeLib(lua_State* L) noexcept {
|
|
||||||
lua_newuserdata(L, 0);
|
|
||||||
|
|
||||||
lua_createtable(L, 0, 0);
|
|
||||||
lua_createtable(L, 0, 0);
|
// ---- time lib ----
|
||||||
{
|
|
||||||
// time.now()
|
// now()
|
||||||
lua_pushcfunction(L, [](auto L) {
|
lua_pushcfunction(L, [](auto L) {
|
||||||
const auto now = nf7::Env::Clock::now().time_since_epoch();
|
const auto now = nf7::Env::Clock::now().time_since_epoch();
|
||||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
|
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
|
||||||
@ -546,6 +478,51 @@ static void PushTimeLib(lua_State* L) noexcept {
|
|||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
lua_setfield(L, -2, "now");
|
lua_setfield(L, -2, "now");
|
||||||
|
|
||||||
|
|
||||||
|
// ---- value lib ----
|
||||||
|
|
||||||
|
// value(entity) -> value
|
||||||
|
lua_pushcfunction(L, [](auto L) {
|
||||||
|
if (lua_isstring(L, 2)) {
|
||||||
|
const auto type = std::string_view {lua_tostring(L, 2)};
|
||||||
|
if (type == "integer" || type == "int") {
|
||||||
|
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
|
||||||
|
} else {
|
||||||
|
return luaL_error(L, "unknown type specifier: %s", type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PushValue(L, CheckValue(L, 1));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
lua_setfield(L, -2, "value");
|
||||||
|
|
||||||
|
// mvector(vector or mutable vector) -> mutable vector
|
||||||
|
lua_pushcfunction(L, [](auto L) {
|
||||||
|
if (auto imm = ToVector(L, 1)) {
|
||||||
|
if (imm->use_count() == 1) {
|
||||||
|
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
|
||||||
|
} else {
|
||||||
|
PushMutableVector(L, std::vector<uint8_t> {**imm});
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
} else if (auto mut = ToMutableVector(L, 1)) {
|
||||||
|
PushMutableVector(L, std::vector<uint8_t> {*mut});
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
PushMutableVector(L, {});
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lua_setfield(L, -2, "mvector");
|
||||||
|
|
||||||
|
|
||||||
|
// ---- bit manip ----
|
||||||
|
luaL_openlibs(L);
|
||||||
|
luaL_loadstring(L, "return require(\"bit\")");
|
||||||
|
lua_call(L, 0, 1);
|
||||||
|
lua_setfield(L, -2, "bit");
|
||||||
}
|
}
|
||||||
lua_setfield(L, -2, "__index");
|
lua_setfield(L, -2, "__index");
|
||||||
lua_setmetatable(L, -2);
|
lua_setmetatable(L, -2);
|
||||||
|
@ -124,4 +124,17 @@ inline nf7::Value CheckValue(lua_State* L, int idx) {
|
|||||||
return std::move(*v);
|
return std::move(*v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void ToStringList(lua_State* L, std::vector<std::string>& v, int idx) {
|
||||||
|
const size_t n = lua_objlen(L, idx);
|
||||||
|
v.clear();
|
||||||
|
v.reserve(n);
|
||||||
|
for (int i = 1; i <= static_cast<int>(n); ++i) {
|
||||||
|
lua_rawgeti(L, idx, i);
|
||||||
|
if (auto str = lua_tostring(L, -1)) {
|
||||||
|
v.push_back(str);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
@ -23,8 +23,6 @@ lua_State* Thread::Init(lua_State* L) noexcept {
|
|||||||
assert(state_ == kInitial);
|
assert(state_ == kInitial);
|
||||||
|
|
||||||
th_ = lua_newthread(L);
|
th_ = lua_newthread(L);
|
||||||
PushImmEnv(L);
|
|
||||||
lua_setfenv(L, -2);
|
|
||||||
th_ref_.emplace(ctx_, ljq_, L);
|
th_ref_.emplace(ctx_, ljq_, L);
|
||||||
|
|
||||||
state_ = kPaused;
|
state_ = kPaused;
|
||||||
@ -36,24 +34,24 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
|
|||||||
if (state_ == kAborted) return;
|
if (state_ == kAborted) return;
|
||||||
assert(L == th_);
|
assert(L == th_);
|
||||||
assert(state_ == kPaused);
|
assert(state_ == kPaused);
|
||||||
(void) L;
|
|
||||||
|
|
||||||
static const auto kHook = [](auto L, auto) {
|
static const auto kHook = [](auto L, auto) {
|
||||||
luaL_error(L, "reached instruction limit (<=1e7)");
|
luaL_error(L, "reached instruction limit (<=1e7)");
|
||||||
};
|
};
|
||||||
lua_sethook(th_, kHook, LUA_MASKCOUNT, kInstructionLimit);
|
lua_sethook(L, kHook, LUA_MASKCOUNT, kInstructionLimit);
|
||||||
|
|
||||||
PushGlobalTable(th_);
|
// set global table
|
||||||
PushWeakPtr(th_, weak_from_this());
|
PushGlobalTable(L);
|
||||||
PushMeta(th_);
|
PushWeakPtr(L, weak_from_this());
|
||||||
lua_setmetatable(th_, -2);
|
PushMeta(L);
|
||||||
lua_setfield(th_, -2, "nf7");
|
lua_setmetatable(L, -2);
|
||||||
lua_pop(th_, 1);
|
lua_setfield(L, -2, "nf7");
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
state_ = kRunning;
|
state_ = kRunning;
|
||||||
k.unlock();
|
k.unlock();
|
||||||
active_ = true;
|
active_ = true;
|
||||||
const auto ret = lua_resume(th_, narg);
|
const auto ret = lua_resume(L, narg);
|
||||||
active_ = false;
|
active_ = false;
|
||||||
k.lock();
|
k.lock();
|
||||||
if (state_ == kAborted) return;
|
if (state_ == kAborted) return;
|
||||||
@ -68,7 +66,7 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
|
|||||||
state_ = kAborted;
|
state_ = kAborted;
|
||||||
}
|
}
|
||||||
if (!std::exchange(skip_handle_, false)) {
|
if (!std::exchange(skip_handle_, false)) {
|
||||||
handler_(*this, th_);
|
handler_(*this, L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Thread::Abort() noexcept {
|
void Thread::Abort() noexcept {
|
||||||
@ -237,13 +235,10 @@ static void PushMeta(lua_State* L) noexcept {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fu.Then([L, th](auto fu) {
|
fu.ThenIf([L, th](auto& p) {
|
||||||
try {
|
|
||||||
const auto& p = fu.value();
|
|
||||||
th->ExecResume(L, p.first, p.second);
|
th->ExecResume(L, p.first, p.second);
|
||||||
} catch (nf7::Exception& e) {
|
}).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) {
|
||||||
th->ExecResume(L);
|
th->ExecResume(L);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
th->ExpectYield(L);
|
th->ExpectYield(L);
|
||||||
return lua_yield(L, 0);
|
return lua_yield(L, 0);
|
||||||
|
@ -138,16 +138,16 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
Thread::Handler Thread::CreatePromiseHandler(
|
Thread::Handler Thread::CreatePromiseHandler(
|
||||||
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
|
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
|
||||||
return [&pro, f = std::move(f)](auto& self, auto L) {
|
return [pro = pro, f = std::move(f)](auto& self, auto L) mutable {
|
||||||
switch (self.state()) {
|
switch (self.state()) {
|
||||||
case kPaused:
|
case kPaused:
|
||||||
pro.Throw(std::make_exception_ptr<nf7::Exception>({"unexpected yield"}));
|
pro.template Throw<nf7::Exception>("unexpected yield");
|
||||||
break;
|
break;
|
||||||
case kFinished:
|
case kFinished:
|
||||||
pro.Wrap([&]() { return f(L); });
|
pro.Wrap([&]() { return f(L); });
|
||||||
break;
|
break;
|
||||||
case kAborted:
|
case kAborted:
|
||||||
pro.Throw(std::make_exception_ptr<nf7::Exception>({lua_tostring(L, -1)}));
|
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
|
109
common/mutex.hh
Normal file
109
common/mutex.hh
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "common/future.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
// nf7::Mutex is not thread-safe.
|
||||||
|
class Mutex final {
|
||||||
|
public:
|
||||||
|
class Lock;
|
||||||
|
|
||||||
|
Mutex() noexcept {
|
||||||
|
}
|
||||||
|
~Mutex() noexcept;
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<Lock>> AcquireLock(bool ex = false) noexcept {
|
||||||
|
if (auto ret = TryAcquireLock(ex)) {
|
||||||
|
return {ret};
|
||||||
|
} else {
|
||||||
|
if (ex || pends_.size() == 0 || pends_.back().ex) {
|
||||||
|
pends_.push_back({.pro = {}, .ex = ex});
|
||||||
|
}
|
||||||
|
return pends_.back().pro.future();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::shared_ptr<Lock> TryAcquireLock(bool ex = false) noexcept {
|
||||||
|
auto k = TryAcquireLock_(ex);
|
||||||
|
if (k) {
|
||||||
|
onLock();
|
||||||
|
}
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void()> onLock = [](){};
|
||||||
|
std::function<void()> onUnlock = [](){};
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ex_ = false;
|
||||||
|
std::weak_ptr<Lock> k_;
|
||||||
|
|
||||||
|
struct Item final {
|
||||||
|
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
|
||||||
|
bool ex;
|
||||||
|
};
|
||||||
|
std::deque<Item> pends_;
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Lock> TryAcquireLock_(bool ex) noexcept {
|
||||||
|
if (auto k = k_.lock()) {
|
||||||
|
if (!ex_ && !ex) {
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
k = std::make_shared<Lock>(*this);
|
||||||
|
ex_ = ex;
|
||||||
|
k_ = k;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Mutex::Lock final {
|
||||||
|
public:
|
||||||
|
friend nf7::Mutex;
|
||||||
|
|
||||||
|
Lock() = delete;
|
||||||
|
Lock(nf7::Mutex& mtx) noexcept : mtx_(&mtx) {
|
||||||
|
}
|
||||||
|
Lock(const Lock&) = delete;
|
||||||
|
Lock(Lock&&) = delete;
|
||||||
|
Lock& operator=(const Lock&) = delete;
|
||||||
|
Lock& operator=(Lock&&) = delete;
|
||||||
|
|
||||||
|
~Lock() noexcept {
|
||||||
|
if (mtx_) {
|
||||||
|
auto& pends = mtx_->pends_;
|
||||||
|
if (pends.size() > 0) {
|
||||||
|
auto item = std::move(pends.front());
|
||||||
|
pends.pop_front();
|
||||||
|
mtx_->ex_ = false;
|
||||||
|
mtx_->k_ = {};
|
||||||
|
|
||||||
|
auto k = mtx_->TryAcquireLock_(item.ex);
|
||||||
|
assert(k);
|
||||||
|
item.pro.Return(std::move(k));
|
||||||
|
} else {
|
||||||
|
mtx_->onUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::Mutex* mtx_;
|
||||||
|
};
|
||||||
|
Mutex::~Mutex() noexcept {
|
||||||
|
pends_.clear();
|
||||||
|
if (auto k = k_.lock()) {
|
||||||
|
k->mtx_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
class NativeFile final : public nf7::Context {
|
class NFile final {
|
||||||
public:
|
public:
|
||||||
class Exception final : public nf7::Exception {
|
class Exception final : public nf7::Exception {
|
||||||
using nf7::Exception::Exception;
|
using nf7::Exception::Exception;
|
||||||
@ -23,17 +23,16 @@ class NativeFile final : public nf7::Context {
|
|||||||
};
|
};
|
||||||
using Flags = uint8_t;
|
using Flags = uint8_t;
|
||||||
|
|
||||||
NativeFile() = delete;
|
NFile() = delete;
|
||||||
NativeFile(nf7::Env& env, nf7::File::Id id,
|
NFile(const std::filesystem::path& path, Flags flags) :
|
||||||
const std::filesystem::path& path, Flags flags) :
|
path_(path), flags_(flags) {
|
||||||
Context(env, id), path_(path), flags_(flags) {
|
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
~NativeFile() noexcept;
|
~NFile() noexcept;
|
||||||
NativeFile(const NativeFile&) = delete;
|
NFile(const NFile&) = delete;
|
||||||
NativeFile(NativeFile&&) = delete;
|
NFile(NFile&&) = delete;
|
||||||
NativeFile& operator=(const NativeFile&) = delete;
|
NFile& operator=(const NFile&) = delete;
|
||||||
NativeFile& operator=(NativeFile&&) = delete;
|
NFile& operator=(NFile&&) = delete;
|
||||||
|
|
||||||
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
||||||
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
@ -1,4 +1,4 @@
|
|||||||
#include "common/native_file.hh"
|
#include "common/nfile.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -11,7 +11,7 @@ extern "C" {
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
void NativeFile::Init() {
|
void NFile::Init() {
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
||||||
flags |= O_RDWR | O_CREAT;
|
flags |= O_RDWR | O_CREAT;
|
||||||
@ -23,45 +23,45 @@ void NativeFile::Init() {
|
|||||||
|
|
||||||
int fd = open(path_.string().c_str(), flags, 0600);
|
int fd = open(path_.string().c_str(), flags, 0600);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
throw NativeFile::Exception {"open failure"};
|
throw NFile::Exception {"open failure"};
|
||||||
}
|
}
|
||||||
handle_ = static_cast<uint64_t>(fd);
|
handle_ = static_cast<uint64_t>(fd);
|
||||||
}
|
}
|
||||||
NativeFile::~NativeFile() noexcept {
|
NFile::~NFile() noexcept {
|
||||||
const auto fd = static_cast<int>(handle_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
if (close(fd) == -1) {
|
if (close(fd) == -1) {
|
||||||
// ;(
|
// ;(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t NativeFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
||||||
const auto fd = static_cast<int>(handle_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
const auto off = static_cast<off_t>(offset);
|
const auto off = static_cast<off_t>(offset);
|
||||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||||
throw NativeFile::Exception {"lseek failure"};
|
throw NFile::Exception {"lseek failure"};
|
||||||
}
|
}
|
||||||
const auto ret = read(fd, buf, size);
|
const auto ret = read(fd, buf, size);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
throw NativeFile::Exception {"read failure"};
|
throw NFile::Exception {"read failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
return static_cast<size_t>(ret);
|
||||||
}
|
}
|
||||||
size_t NativeFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
||||||
const auto fd = static_cast<int>(handle_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
const auto off = static_cast<off_t>(offset);
|
const auto off = static_cast<off_t>(offset);
|
||||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||||
throw nf7::NativeFile::Exception {"lseek failure"};
|
throw nf7::NFile::Exception {"lseek failure"};
|
||||||
}
|
}
|
||||||
const auto ret = write(fd, buf, size);
|
const auto ret = write(fd, buf, size);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
throw nf7::NativeFile::Exception {"write failure"};
|
throw nf7::NFile::Exception {"write failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
return static_cast<size_t>(ret);
|
||||||
}
|
}
|
||||||
size_t NativeFile::Truncate(size_t size) {
|
size_t NFile::Truncate(size_t size) {
|
||||||
const auto fd = static_cast<int>(handle_);
|
const auto fd = static_cast<int>(handle_);
|
||||||
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
||||||
throw nf7::NativeFile::Exception {"ftruncate failure"};
|
throw nf7::NFile::Exception {"ftruncate failure"};
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
46
common/nfile_watcher.hh
Normal file
46
common/nfile_watcher.hh
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "common/file_base.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
class NFileWatcher final : public nf7::FileBase::Feature {
|
||||||
|
public:
|
||||||
|
NFileWatcher() = default;
|
||||||
|
NFileWatcher(const NFileWatcher&) = delete;
|
||||||
|
NFileWatcher(NFileWatcher&&) = delete;
|
||||||
|
NFileWatcher& operator=(const NFileWatcher&) = delete;
|
||||||
|
NFileWatcher& operator=(NFileWatcher&&) = delete;
|
||||||
|
|
||||||
|
void Watch(const std::filesystem::path& npath) noexcept {
|
||||||
|
npath_ = npath;
|
||||||
|
lastmod_ = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void()> onMod;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void Update() noexcept override
|
||||||
|
try {
|
||||||
|
if (npath_) {
|
||||||
|
const auto lastmod = std::filesystem::last_write_time(*npath_);
|
||||||
|
if (lastmod_ && lastmod > *lastmod_) {
|
||||||
|
onMod();
|
||||||
|
}
|
||||||
|
lastmod_ = lastmod;
|
||||||
|
}
|
||||||
|
} catch (std::filesystem::filesystem_error&) {
|
||||||
|
lastmod_.emplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<std::filesystem::path> npath_;
|
||||||
|
std::optional<std::filesystem::file_time_type> lastmod_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
@ -1,4 +1,4 @@
|
|||||||
#include "common/native_file.hh"
|
#include "common/nfile.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@ -7,7 +7,7 @@ extern "C" {
|
|||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
|
|
||||||
void NativeFile::Init() {
|
void NFile::Init() {
|
||||||
DWORD acc = 0;
|
DWORD acc = 0;
|
||||||
DWORD flags = 0;
|
DWORD flags = 0;
|
||||||
if (flags_ & kRead) {
|
if (flags_ & kRead) {
|
||||||
@ -24,55 +24,55 @@ void NativeFile::Init() {
|
|||||||
path_.string().c_str(),
|
path_.string().c_str(),
|
||||||
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
|
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (h == INVALID_HANDLE_VALUE) {
|
if (h == INVALID_HANDLE_VALUE) {
|
||||||
throw NativeFile::Exception {"open failure"};
|
throw NFile::Exception {"open failure"};
|
||||||
}
|
}
|
||||||
handle_ = reinterpret_cast<uintptr_t>(h);
|
handle_ = reinterpret_cast<uintptr_t>(h);
|
||||||
}
|
}
|
||||||
NativeFile::~NativeFile() noexcept {
|
NFile::~NFile() noexcept {
|
||||||
auto h = reinterpret_cast<HANDLE>(handle_);
|
auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
if (!CloseHandle(h)) {
|
if (!CloseHandle(h)) {
|
||||||
// ;(
|
// ;(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t NativeFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
||||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = offset & 0xFFFFFFFF;
|
LONG off_low = offset & 0xFFFFFFFF;
|
||||||
LONG off_high = offset >> 32;
|
LONG off_high = offset >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||||
throw NativeFile::Exception {"failed to set file pointer"};
|
throw NFile::Exception {"failed to set file pointer"};
|
||||||
}
|
}
|
||||||
DWORD ret;
|
DWORD ret;
|
||||||
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
||||||
throw NativeFile::Exception {"read failure"};
|
throw NFile::Exception {"read failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
return static_cast<size_t>(ret);
|
||||||
}
|
}
|
||||||
size_t NativeFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
||||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = offset & 0xFFFFFFFF;
|
LONG off_low = offset & 0xFFFFFFFF;
|
||||||
LONG off_high = offset >> 32;
|
LONG off_high = offset >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||||
throw NativeFile::Exception {"failed to set file pointer"};
|
throw NFile::Exception {"failed to set file pointer"};
|
||||||
}
|
}
|
||||||
DWORD ret;
|
DWORD ret;
|
||||||
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
||||||
throw NativeFile::Exception {"read failure"};
|
throw NFile::Exception {"read failure"};
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(ret);
|
return static_cast<size_t>(ret);
|
||||||
}
|
}
|
||||||
size_t NativeFile::Truncate(size_t size) {
|
size_t NFile::Truncate(size_t size) {
|
||||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||||
|
|
||||||
LONG off_low = size & 0xFFFFFFFF;
|
LONG off_low = size & 0xFFFFFFFF;
|
||||||
LONG off_high = size >> 32;
|
LONG off_high = size >> 32;
|
||||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||||
throw NativeFile::Exception {"failed to set file pointer"};
|
throw NFile::Exception {"failed to set file pointer"};
|
||||||
}
|
}
|
||||||
if (!SetEndOfFile(h)) {
|
if (!SetEndOfFile(h)) {
|
||||||
throw NativeFile::Exception {"SetEndOfFile failure"};
|
throw NFile::Exception {"SetEndOfFile failure"};
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -38,7 +39,7 @@ class NodeRootSelectLambda : public nf7::Node::Lambda,
|
|||||||
const auto ks = std::string {k};
|
const auto ks = std::string {k};
|
||||||
if (names_.contains(ks)) {
|
if (names_.contains(ks)) {
|
||||||
names_.clear();
|
names_.clear();
|
||||||
auto pro = std::move(*pro_);
|
auto pro = *std::exchange(pro_, std::nullopt);
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
pro.Return({ks, v});
|
pro.Return({ks, v});
|
||||||
} else {
|
} else {
|
||||||
|
102
common/ring_buffer.hh
Normal file
102
common/ring_buffer.hh
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
class RingBuffer final {
|
||||||
|
public:
|
||||||
|
RingBuffer() = delete;
|
||||||
|
RingBuffer(uint64_t unit, uint64_t bufn) noexcept :
|
||||||
|
buf_(unit*bufn), unit_(unit), bufn_(bufn) {
|
||||||
|
}
|
||||||
|
RingBuffer(const RingBuffer&) = delete;
|
||||||
|
RingBuffer(RingBuffer&&) = default;
|
||||||
|
RingBuffer& operator=(const RingBuffer&) = delete;
|
||||||
|
RingBuffer& operator=(RingBuffer&&) = default;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
uint64_t Mix(uint64_t begin, const T* ptr, uint64_t n) noexcept {
|
||||||
|
assert(unit_ == sizeof(T));
|
||||||
|
|
||||||
|
if (begin < cur_) {
|
||||||
|
const auto drop = cur_ - begin;
|
||||||
|
if (drop >= n) {
|
||||||
|
return cur_;
|
||||||
|
}
|
||||||
|
ptr += drop;
|
||||||
|
n -= drop;
|
||||||
|
begin = cur_;
|
||||||
|
}
|
||||||
|
if (begin > cur_) {
|
||||||
|
const auto skip = begin - cur_;
|
||||||
|
n = std::min(bufn_ - skip, n);
|
||||||
|
}
|
||||||
|
auto buf = reinterpret_cast<T*>(buf_.data());
|
||||||
|
|
||||||
|
const auto [c, r, l] = CalcCursor(begin, n);
|
||||||
|
for (uint64_t i = 0; i < r; ++i) {
|
||||||
|
buf[c+i] += ptr[i];
|
||||||
|
}
|
||||||
|
for (uint64_t i = 0; i < l; ++i) {
|
||||||
|
buf[i] += ptr[r+i];
|
||||||
|
}
|
||||||
|
return begin + n;
|
||||||
|
}
|
||||||
|
void Take(uint8_t* ptr, uint64_t n) noexcept {
|
||||||
|
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||||
|
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||||
|
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||||
|
std::memset(&buf_[c*unit_], 0, r*unit_);
|
||||||
|
std::memset(&buf_[0*unit_], 0, l*unit_);
|
||||||
|
cur_ += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t Peek(uint64_t begin, uint8_t* ptr, uint64_t n) noexcept {
|
||||||
|
if (cur_ > bufn_) {
|
||||||
|
const auto actual_begin = std::max(begin, cur_-bufn_);
|
||||||
|
const auto pad = std::min(n, actual_begin - begin);
|
||||||
|
std::memset(ptr, 0, pad*unit_);
|
||||||
|
begin = actual_begin;
|
||||||
|
ptr += pad*unit_;
|
||||||
|
n -= pad;
|
||||||
|
}
|
||||||
|
n = std::min(n, bufn_);
|
||||||
|
|
||||||
|
const auto [c, r, l] = CalcCursor(begin, n);
|
||||||
|
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||||
|
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||||
|
return begin + n;
|
||||||
|
}
|
||||||
|
void Write(const uint8_t* ptr, uint64_t n) noexcept {
|
||||||
|
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||||
|
std::memcpy(&buf_[c*unit_], &ptr[0*unit_], r*unit_);
|
||||||
|
std::memcpy(&buf_[0*unit_], &ptr[r*unit_], l*unit_);
|
||||||
|
cur_ += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t unit() const noexcept { return unit_; }
|
||||||
|
uint64_t bufn() const noexcept { return bufn_; }
|
||||||
|
uint64_t cur() const noexcept { return cur_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> buf_;
|
||||||
|
uint64_t unit_;
|
||||||
|
uint64_t bufn_;
|
||||||
|
uint64_t cur_ = 0;
|
||||||
|
|
||||||
|
std::tuple<uint64_t, uint64_t, uint64_t> CalcCursor(
|
||||||
|
uint64_t t, uint64_t n) noexcept {
|
||||||
|
assert(n <= bufn_);
|
||||||
|
const auto c = t % bufn_;
|
||||||
|
const auto r = std::min(bufn_ - c, n);
|
||||||
|
const auto l = n > r? n - r: 0;
|
||||||
|
return {c, r, l};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
95
common/task.hh
Normal file
95
common/task.hh
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/future.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Task : public nf7::Context,
|
||||||
|
public std::enable_shared_from_this<Task<T>> {
|
||||||
|
public:
|
||||||
|
class Holder;
|
||||||
|
|
||||||
|
using Future = nf7::Future<T>;
|
||||||
|
using Coro = typename Future::Coro;
|
||||||
|
|
||||||
|
using nf7::Context::Context;
|
||||||
|
|
||||||
|
Task(const Task&) = delete;
|
||||||
|
Task(Task&&) = delete;
|
||||||
|
Task& operator=(const Task&) = delete;
|
||||||
|
Task& operator=(Task&&) = delete;
|
||||||
|
|
||||||
|
void Start() noexcept {
|
||||||
|
coro_ = Proc();
|
||||||
|
fu_ = coro_->Start(self());
|
||||||
|
}
|
||||||
|
void Abort() noexcept {
|
||||||
|
coro_->Abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto self() noexcept {
|
||||||
|
return std::enable_shared_from_this<Task<T>>::shared_from_this();
|
||||||
|
}
|
||||||
|
std::optional<Future>& fu() noexcept { return *fu_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual Coro Proc() noexcept = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<Coro> coro_;
|
||||||
|
std::optional<Future> fu_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// all operations are not thread-safe
|
||||||
|
template <typename T>
|
||||||
|
class Task<T>::Holder final {
|
||||||
|
public:
|
||||||
|
Holder() = default;
|
||||||
|
~Holder() noexcept {
|
||||||
|
Abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
Holder(const Holder&) = delete;
|
||||||
|
Holder(Holder&&) = delete;
|
||||||
|
Holder& operator=(const Holder&) = delete;
|
||||||
|
Holder& operator=(Holder&&) = delete;
|
||||||
|
|
||||||
|
bool CleanUp() noexcept {
|
||||||
|
return !!std::exchange(fu_, std::nullopt);
|
||||||
|
}
|
||||||
|
void Abort() noexcept {
|
||||||
|
if (auto task = task_.lock()) {
|
||||||
|
task->Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U, typename... Args>
|
||||||
|
nf7::Future<T> StartIf(Args&&... args) noexcept {
|
||||||
|
if (fu_) return *fu_;
|
||||||
|
|
||||||
|
auto task = std::make_shared<U>(std::forward<Args>(args)...);
|
||||||
|
task->Start();
|
||||||
|
|
||||||
|
task_ = task;
|
||||||
|
fu_ = task->fu();
|
||||||
|
return *fu_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<nf7::Future<T>>& fu() noexcept { return fu_; }
|
||||||
|
const std::optional<nf7::Future<T>>& fu() const noexcept { return fu_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<Task<T>> task_;
|
||||||
|
|
||||||
|
std::optional<nf7::Future<T>> fu_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
@ -25,7 +25,6 @@ class Thread final : public nf7::Context,
|
|||||||
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner) noexcept :
|
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner) noexcept :
|
||||||
nf7::Context(env, id), env_(&env), runner_(std::move(runner)) {
|
nf7::Context(env, id), env_(&env), runner_(std::move(runner)) {
|
||||||
}
|
}
|
||||||
virtual ~Thread() noexcept = default;
|
|
||||||
Thread(const Thread&) = delete;
|
Thread(const Thread&) = delete;
|
||||||
Thread(Thread&&) = delete;
|
Thread(Thread&&) = delete;
|
||||||
Thread& operator=(const Thread&) = delete;
|
Thread& operator=(const Thread&) = delete;
|
||||||
|
@ -89,12 +89,12 @@ class TimedWaitQueue final : private TimedQueue<T> {
|
|||||||
void Notify() noexcept {
|
void Notify() noexcept {
|
||||||
cv_.notify_all();
|
cv_.notify_all();
|
||||||
}
|
}
|
||||||
void Wait() noexcept {
|
void Wait(const auto& dur) noexcept {
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
std::unique_lock<std::mutex> k(mtx_);
|
||||||
if (auto t = next_()) {
|
if (auto t = next_()) {
|
||||||
cv_.wait_until(k, *t);
|
cv_.wait_until(k, *t);
|
||||||
} else {
|
} else {
|
||||||
cv_.wait(k);
|
cv_.wait_for(k, dur);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
common/util_algorithm.hh
Normal file
23
common/util_algorithm.hh
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7::util {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline size_t Uniq(std::vector<T>& v) noexcept {
|
||||||
|
size_t n = 0;
|
||||||
|
for (auto itr = v.begin(); itr < v.end();) {
|
||||||
|
if (v.end() != std::find(itr+1, v.end(), *itr)) {
|
||||||
|
itr = v.erase(itr);
|
||||||
|
++n;
|
||||||
|
} else {
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nf7::util
|
@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -10,6 +12,18 @@
|
|||||||
|
|
||||||
namespace nf7::util {
|
namespace nf7::util {
|
||||||
|
|
||||||
|
inline std::string_view Trim(
|
||||||
|
std::string_view str,
|
||||||
|
const std::function<bool(char)>& func = [](auto c) { return std::isspace(c); }) noexcept {
|
||||||
|
while (!str.empty() && func(str.front())) {
|
||||||
|
str.remove_prefix(1);
|
||||||
|
}
|
||||||
|
while (!str.empty() && func(str.back())) {
|
||||||
|
str.remove_suffix(1);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
|
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
|
||||||
std::string_view ret;
|
std::string_view ret;
|
||||||
while (ret.empty() && i < str.size()) {
|
while (ret.empty() && i < str.size()) {
|
||||||
@ -35,15 +49,6 @@ inline void JoinAndAppend(std::string& dst, std::span<const std::string> src, ch
|
|||||||
dst += c;
|
dst += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline void Uniq(std::vector<std::string>& v) noexcept {
|
|
||||||
for (auto itr = v.begin(); itr < v.end();) {
|
|
||||||
if (v.end() != std::find(itr+1, v.end(), *itr)) {
|
|
||||||
itr = v.erase(itr);
|
|
||||||
} else {
|
|
||||||
++itr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::optional<std::string_view> SplitAndValidate(
|
inline std::optional<std::string_view> SplitAndValidate(
|
||||||
std::string_view v,
|
std::string_view v,
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include <miniaudio.h>
|
|
||||||
#include <yas/serialize.hpp>
|
|
||||||
|
|
||||||
#include "nf7.hh"
|
|
||||||
|
|
||||||
|
|
||||||
namespace yas::detail {
|
|
||||||
|
|
||||||
template <size_t F>
|
|
||||||
struct serializer<
|
|
||||||
type_prop::not_a_fundamental,
|
|
||||||
ser_case::use_internal_serializer,
|
|
||||||
F,
|
|
||||||
ma_device_type> {
|
|
||||||
public:
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& save(Archive& ar, const ma_device_type& t) {
|
|
||||||
switch (t) {
|
|
||||||
case ma_device_type_playback:
|
|
||||||
ar("playback");
|
|
||||||
break;
|
|
||||||
case ma_device_type_capture:
|
|
||||||
ar("capture");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& load(Archive& ar, ma_device_type& t) {
|
|
||||||
std::string v;
|
|
||||||
ar(v);
|
|
||||||
if (v == "playback") {
|
|
||||||
t = ma_device_type_playback;
|
|
||||||
} else if (v == "capture") {
|
|
||||||
t = ma_device_type_capture;
|
|
||||||
} else {
|
|
||||||
throw nf7::DeserializeException("unknown device type");
|
|
||||||
}
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t F>
|
|
||||||
struct serializer<
|
|
||||||
type_prop::not_a_fundamental,
|
|
||||||
ser_case::use_internal_serializer,
|
|
||||||
F,
|
|
||||||
ma_device_config> {
|
|
||||||
public:
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& save(Archive& ar, const ma_device_config& v) {
|
|
||||||
serialize(ar, v);
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
template <typename Archive>
|
|
||||||
static Archive& load(Archive& ar, ma_device_config& v) {
|
|
||||||
serialize(ar, v);
|
|
||||||
if (v.sampleRate == 0) {
|
|
||||||
throw nf7::DeserializeException("invalid sample rate");
|
|
||||||
}
|
|
||||||
return ar;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void serialize(auto& ar, auto& v) {
|
|
||||||
ar(v.deviceType);
|
|
||||||
ar(v.sampleRate);
|
|
||||||
if (v.deviceType == ma_device_type_playback) {
|
|
||||||
ar(v.playback.format);
|
|
||||||
ar(v.playback.channels);
|
|
||||||
} else if (v.deviceType == ma_device_type_capture) {
|
|
||||||
ar(v.capture.format);
|
|
||||||
ar(v.capture.channels);
|
|
||||||
} else {
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace yas::detail
|
|
34
common/yas_enum.hh
Normal file
34
common/yas_enum.hh
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
#include <yas/serialize.hpp>
|
||||||
|
#include <yas/types/std/string.hpp>
|
||||||
|
|
||||||
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct EnumSerializer {
|
||||||
|
public:
|
||||||
|
static auto& save(auto& ar, auto t) {
|
||||||
|
ar(magic_enum::enum_name(t));
|
||||||
|
return ar;
|
||||||
|
}
|
||||||
|
static auto& load(auto& ar, auto& t) {
|
||||||
|
std::string v;
|
||||||
|
ar(v);
|
||||||
|
if (auto ot = magic_enum::enum_cast<T>(v)) {
|
||||||
|
t = *ot;
|
||||||
|
} else {
|
||||||
|
throw nf7::DeserializeException {"unknown enum: "+v};
|
||||||
|
}
|
||||||
|
return ar;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nf7
|
@ -1,4 +1,6 @@
|
|||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cinttypes>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@ -32,7 +34,11 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
|
|||||||
|
|
||||||
class Queue;
|
class Queue;
|
||||||
|
|
||||||
AudioContext(nf7::Env&) noexcept;
|
AudioContext(Env& env) noexcept :
|
||||||
|
nf7::File(kType, env),
|
||||||
|
nf7::DirItem(DirItem::kMenu | DirItem::kTooltip),
|
||||||
|
q_(std::make_shared<AudioContext::Queue>(*this)) {
|
||||||
|
}
|
||||||
|
|
||||||
AudioContext(nf7::Deserializer& ar) noexcept : AudioContext(ar.env()) {
|
AudioContext(nf7::Deserializer& ar) noexcept : AudioContext(ar.env()) {
|
||||||
}
|
}
|
||||||
@ -42,10 +48,11 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
|
|||||||
return std::make_unique<AudioContext>(env);
|
return std::make_unique<AudioContext>(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() noexcept override;
|
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateTooltip() noexcept override;
|
void UpdateTooltip() noexcept override;
|
||||||
|
|
||||||
|
static void UpdateDeviceListMenu(ma_device_info*, ma_uint32) noexcept;
|
||||||
|
|
||||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<
|
return nf7::InterfaceSelector<
|
||||||
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
|
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
|
||||||
@ -55,51 +62,54 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
|
|||||||
std::shared_ptr<Queue> q_;
|
std::shared_ptr<Queue> q_;
|
||||||
|
|
||||||
const char* popup_ = nullptr;
|
const char* popup_ = nullptr;
|
||||||
|
|
||||||
|
|
||||||
// for device list popup
|
|
||||||
struct DeviceList {
|
|
||||||
std::atomic<bool> working;
|
|
||||||
bool success;
|
|
||||||
ma_device_info* play;
|
|
||||||
ma_uint32 play_n;
|
|
||||||
ma_device_info* cap;
|
|
||||||
ma_uint32 cap_n;
|
|
||||||
};
|
|
||||||
std::shared_ptr<DeviceList> devlist_;
|
|
||||||
|
|
||||||
|
|
||||||
void UpdateDeviceList(const ma_device_info*, size_t n) noexcept;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioContext::Queue final : public nf7::audio::Queue,
|
class AudioContext::Queue final : public nf7::audio::Queue,
|
||||||
public std::enable_shared_from_this<AudioContext::Queue> {
|
public std::enable_shared_from_this<AudioContext::Queue> {
|
||||||
public:
|
public:
|
||||||
struct Runner final {
|
|
||||||
Runner(Queue& owner) noexcept : owner_(&owner) {
|
|
||||||
}
|
|
||||||
void operator()(Task&& t) {
|
|
||||||
t(owner_->ctx_.get());
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
Queue* const owner_;
|
|
||||||
};
|
|
||||||
using Thread = nf7::Thread<Runner, Task>;
|
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
kInitializing,
|
kInitial,
|
||||||
kReady,
|
kReady,
|
||||||
kBroken,
|
kBroken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ThreadData {
|
||||||
|
public:
|
||||||
|
std::atomic<State> state = kInitial;
|
||||||
|
ma_context ctx;
|
||||||
|
};
|
||||||
|
struct Runner {
|
||||||
|
public:
|
||||||
|
Runner(const std::shared_ptr<ThreadData>& tdata) noexcept : tdata_(tdata) {
|
||||||
|
}
|
||||||
|
void operator()(Task&& t) {
|
||||||
|
if (tdata_->state != kBroken) {
|
||||||
|
t(&tdata_->ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::shared_ptr<ThreadData> tdata_;
|
||||||
|
};
|
||||||
|
using Thread = nf7::Thread<Runner, Task>;
|
||||||
|
|
||||||
Queue() = delete;
|
Queue() = delete;
|
||||||
Queue(AudioContext& f) noexcept :
|
Queue(AudioContext& f) noexcept :
|
||||||
env_(&f.env()), th_(std::make_shared<Thread>(f, Runner {*this})) {
|
env_(&f.env()),
|
||||||
|
tdata_(std::make_shared<ThreadData>()),
|
||||||
|
th_(std::make_shared<Thread>(f, Runner {tdata_})) {
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(f.env(), 0, "creating ma_context");
|
||||||
|
th_->Push(ctx, [tdata = tdata_](auto) {
|
||||||
|
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, &tdata->ctx)) {
|
||||||
|
tdata->state = kReady;
|
||||||
|
} else {
|
||||||
|
tdata->state = kBroken;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
~Queue() noexcept {
|
~Queue() noexcept {
|
||||||
th_->Push(
|
th_->Push(
|
||||||
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
|
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
|
||||||
[ctx = std::move(ctx_)](auto) { if (ctx) ma_context_uninit(ctx.get()); }
|
[](auto ma) { ma_context_uninit(ma); }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Queue(const Queue&) = delete;
|
Queue(const Queue&) = delete;
|
||||||
@ -107,122 +117,99 @@ class AudioContext::Queue final : public nf7::audio::Queue,
|
|||||||
Queue& operator=(const Queue&) = delete;
|
Queue& operator=(const Queue&) = delete;
|
||||||
Queue& operator=(Queue&&) = delete;
|
Queue& operator=(Queue&&) = delete;
|
||||||
|
|
||||||
void Init() noexcept {
|
|
||||||
th_->Push(
|
|
||||||
std::make_shared<nf7::GenericContext>(*env_, 0, "creating ma_context"),
|
|
||||||
[this, self = shared_from_this()](auto) {
|
|
||||||
auto ctx = std::make_shared<ma_context>();
|
|
||||||
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, ctx.get())) {
|
|
||||||
ctx_ = std::move(ctx);
|
|
||||||
state_ = kReady;
|
|
||||||
} else {
|
|
||||||
state_ = kBroken;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
|
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
|
||||||
th_->Push(ctx, std::move(task));
|
th_->Push(ctx, std::move(task));
|
||||||
}
|
}
|
||||||
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
|
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
|
||||||
|
|
||||||
State state() const noexcept { return state_; }
|
State state() const noexcept { return tdata_->state; }
|
||||||
size_t tasksDone() const noexcept { return th_->tasksDone(); }
|
size_t tasksDone() const noexcept { return th_->tasksDone(); }
|
||||||
|
|
||||||
|
// thread-safe
|
||||||
|
ma_context* ctx() const noexcept {
|
||||||
|
return state() == kReady? &tdata_->ctx: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Env* const env_;
|
Env* const env_;
|
||||||
|
|
||||||
|
std::shared_ptr<ThreadData> tdata_;
|
||||||
std::shared_ptr<Thread> th_;
|
std::shared_ptr<Thread> th_;
|
||||||
|
|
||||||
std::atomic<State> state_ = kInitializing;
|
|
||||||
std::shared_ptr<ma_context> ctx_;
|
|
||||||
};
|
};
|
||||||
AudioContext::AudioContext(Env& env) noexcept :
|
|
||||||
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
|
|
||||||
q_(std::make_shared<Queue>(*this)) {
|
|
||||||
q_->Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AudioContext::Update() noexcept {
|
|
||||||
if (auto popup = std::exchange(popup_, nullptr)) {
|
|
||||||
ImGui::OpenPopup(popup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopup("DeviceList")) {
|
|
||||||
auto& p = devlist_;
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("Audio/Context: device list");
|
|
||||||
if (ImGui::IsWindowAppearing()) {
|
|
||||||
if (!p) {
|
|
||||||
p = std::make_shared<DeviceList>();
|
|
||||||
}
|
|
||||||
p->working = true;
|
|
||||||
q_->Push(
|
|
||||||
std::make_shared<nf7::GenericContext>(*this, "fetching audio device list"),
|
|
||||||
[p](auto ctx) {
|
|
||||||
p->success = false;
|
|
||||||
if (ctx) {
|
|
||||||
const auto ret = ma_context_get_devices(
|
|
||||||
ctx, &p->play, &p->play_n, &p->cap, &p->cap_n);
|
|
||||||
p->success = ret == MA_SUCCESS;
|
|
||||||
}
|
|
||||||
p->working = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Indent();
|
|
||||||
if (p->working) {
|
|
||||||
ImGui::TextUnformatted("fetching audio devices... :)");
|
|
||||||
} else {
|
|
||||||
if (p->success) {
|
|
||||||
ImGui::TextUnformatted("playback:");
|
|
||||||
ImGui::Indent();
|
|
||||||
UpdateDeviceList(p->play, p->play_n);
|
|
||||||
ImGui::Unindent();
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("capture:");
|
|
||||||
ImGui::Indent();
|
|
||||||
UpdateDeviceList(p->cap, p->cap_n);
|
|
||||||
ImGui::Unindent();
|
|
||||||
} else {
|
|
||||||
ImGui::TextUnformatted("failed to fetch devices X(");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::Unindent();
|
|
||||||
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioContext::UpdateMenu() noexcept {
|
void AudioContext::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("display available devices")) {
|
ma_device_info* pbs;
|
||||||
popup_ = "DeviceList";
|
ma_uint32 pbn;
|
||||||
|
ma_device_info* cps;
|
||||||
|
ma_uint32 cpn;
|
||||||
|
if (ImGui::BeginMenu("playback devices")) {
|
||||||
|
auto ma = q_->ctx();
|
||||||
|
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
|
||||||
|
UpdateDeviceListMenu(pbs, pbn);
|
||||||
|
} else {
|
||||||
|
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginMenu("capture devices")) {
|
||||||
|
auto ma = q_->ctx();
|
||||||
|
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
|
||||||
|
UpdateDeviceListMenu(cps, cpn);
|
||||||
|
} else {
|
||||||
|
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AudioContext::UpdateDeviceListMenu(ma_device_info* ptr, ma_uint32 n) noexcept {
|
||||||
|
for (ma_uint32 i = 0; i < n; ++i) {
|
||||||
|
const auto name = std::to_string(i) + ": " + ptr[i].name;
|
||||||
|
if (ImGui::MenuItem(name.c_str())) {
|
||||||
|
ImGui::SetClipboardText(ptr[i].name);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
|
||||||
|
ImGui::Text("index : %" PRIu32, i);
|
||||||
|
ImGui::Text("name : %s", ptr[i].name);
|
||||||
|
ImGui::TextDisabled(" click to copy the name");
|
||||||
|
|
||||||
|
ImGui::Text("default: %s", ptr[i].isDefault? "yes": "no");
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("native formats:");
|
||||||
|
const auto n = std::min(ptr[i].nativeDataFormatCount, ma_uint32 {5});
|
||||||
|
for (ma_uint32 j = 0; j < n; ++j) {
|
||||||
|
const auto& d = ptr[i].nativeDataFormats[j];
|
||||||
|
const char* fmt =
|
||||||
|
d.format == ma_format_u8? "u8":
|
||||||
|
d.format == ma_format_s16? "s16":
|
||||||
|
d.format == ma_format_s24? "s24":
|
||||||
|
d.format == ma_format_s32? "s32":
|
||||||
|
d.format == ma_format_f32? "f32":
|
||||||
|
"unknown";
|
||||||
|
ImGui::Bullet();
|
||||||
|
ImGui::Text("%s / %" PRIu32 " ch / %" PRIu32 " Hz", fmt, d.channels, d.sampleRate);
|
||||||
|
}
|
||||||
|
if (ptr[i].nativeDataFormatCount > n) {
|
||||||
|
ImGui::Bullet(); ImGui::TextDisabled("etc...");
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
ImGui::Bullet(); ImGui::TextDisabled("(nothing)");
|
||||||
|
}
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioContext::UpdateTooltip() noexcept {
|
void AudioContext::UpdateTooltip() noexcept {
|
||||||
const auto state = q_->state();
|
const auto state = q_->state();
|
||||||
const char* state_str =
|
const char* state_str =
|
||||||
state == Queue::kInitializing? "initializing":
|
state == Queue::kInitial? "initializing":
|
||||||
state == Queue::kReady ? "ready":
|
state == Queue::kReady ? "ready":
|
||||||
state == Queue::kBroken ? "broken": "unknown";
|
state == Queue::kBroken ? "broken": "unknown";
|
||||||
ImGui::Text("state: %s", state_str);
|
ImGui::Text("state: %s", state_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioContext::UpdateDeviceList(const ma_device_info* p, size_t n) noexcept {
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
|
||||||
const auto& info = p[i];
|
|
||||||
const auto name = std::to_string(i) + ": " + info.name;
|
|
||||||
ImGui::Selectable(name.c_str(), false, ImGuiSelectableFlags_DontClosePopups);
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::BeginTooltip();
|
|
||||||
ImGui::Text("index : %zu", i);
|
|
||||||
ImGui::Text("name : %s", info.name);
|
|
||||||
ImGui::Text("default : %s", info.isDefault? "true": "false");
|
|
||||||
ImGui::EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} // namespace nf7
|
} // namespace nf7
|
||||||
|
1004
file/audio_device.cc
1004
file/audio_device.cc
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@
|
|||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/luajit.hh"
|
||||||
#include "common/luajit_queue.hh"
|
#include "common/luajit_queue.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/queue.hh"
|
#include "common/queue.hh"
|
||||||
@ -19,7 +20,7 @@ namespace {
|
|||||||
|
|
||||||
class LuaContext final : public nf7::File, public nf7::DirItem {
|
class LuaContext final : public nf7::File, public nf7::DirItem {
|
||||||
public:
|
public:
|
||||||
static inline const GenericTypeInfo<LuaContext> kType = {
|
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
|
||||||
"LuaJIT/Context", {"nf7::DirItem",}};
|
"LuaJIT/Context", {"nf7::DirItem",}};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
static void UpdateTypeTooltip() noexcept {
|
||||||
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
|
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
|
||||||
@ -33,22 +34,20 @@ class LuaContext final : public nf7::File, public nf7::DirItem {
|
|||||||
|
|
||||||
class Queue;
|
class Queue;
|
||||||
|
|
||||||
LuaContext(nf7::Env& env) :
|
LuaContext(nf7::Env& env);
|
||||||
File(kType, env), DirItem(DirItem::kTooltip) {
|
|
||||||
q_ = std::make_shared<Queue>(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
|
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
|
||||||
}
|
}
|
||||||
void Serialize(Serializer&) const noexcept override {
|
void Serialize(nf7::Serializer&) const noexcept override {
|
||||||
}
|
}
|
||||||
std::unique_ptr<File> Clone(Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<LuaContext>(env);
|
return std::make_unique<LuaContext>(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateTooltip() noexcept override;
|
void UpdateTooltip() noexcept override;
|
||||||
|
|
||||||
File::Interface* interface(const std::type_info& t) noexcept override {
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
return nf7::InterfaceSelector<
|
return nf7::InterfaceSelector<
|
||||||
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
|
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
|
||||||
}
|
}
|
||||||
@ -61,21 +60,33 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
|
|||||||
public std::enable_shared_from_this<LuaContext::Queue> {
|
public std::enable_shared_from_this<LuaContext::Queue> {
|
||||||
public:
|
public:
|
||||||
struct Runner final {
|
struct Runner final {
|
||||||
Runner(Queue& owner) noexcept : owner_(&owner) {
|
Runner(std::weak_ptr<Queue> owner) noexcept : owner_(owner) {
|
||||||
}
|
}
|
||||||
void operator()(Task&& t) {
|
void operator()(Task&& t) {
|
||||||
t(owner_->L);
|
if (auto k = owner_.lock()) {
|
||||||
|
t(k->L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
Queue* const owner_;
|
std::weak_ptr<Queue> owner_;
|
||||||
};
|
};
|
||||||
using Thread = nf7::Thread<Runner, Task>;
|
using Thread = nf7::Thread<Runner, Task>;
|
||||||
|
|
||||||
|
static std::shared_ptr<Queue> Create(LuaContext& f) {
|
||||||
|
auto ret = std::make_shared<Queue>(f);
|
||||||
|
ret->th_ = std::make_shared<Thread>(f, Runner {ret});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
Queue() = delete;
|
Queue() = delete;
|
||||||
Queue(LuaContext& f) :
|
Queue(LuaContext& f) : L(luaL_newstate()), env_(&f.env()) {
|
||||||
L(luaL_newstate()), env_(&f.env()),
|
if (!L) {
|
||||||
th_(std::make_shared<Thread>(f, Runner {*this})) {
|
throw nf7::Exception("failed to create new Lua state");
|
||||||
if (!L) throw nf7::Exception("failed to create new Lua state");
|
}
|
||||||
|
lua_pushthread(L);
|
||||||
|
nf7::luajit::PushImmEnv(L);
|
||||||
|
lua_setfenv(L, -2);
|
||||||
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
~Queue() noexcept {
|
~Queue() noexcept {
|
||||||
th_->Push(
|
th_->Push(
|
||||||
@ -100,7 +111,22 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
|
|||||||
Env* const env_;
|
Env* const env_;
|
||||||
std::shared_ptr<Thread> th_;
|
std::shared_ptr<Thread> th_;
|
||||||
};
|
};
|
||||||
|
LuaContext::LuaContext(nf7::Env& env) :
|
||||||
|
nf7::File(kType, env),
|
||||||
|
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip),
|
||||||
|
q_(Queue::Create(*this)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LuaContext::UpdateMenu() noexcept {
|
||||||
|
if (ImGui::MenuItem("perform a full GC cycle")) {
|
||||||
|
q_->Push(
|
||||||
|
std::make_shared<nf7::GenericContext>(*this, "LuaJIT garbage collection"),
|
||||||
|
[](auto L) {
|
||||||
|
lua_gc(L, LUA_GCCOLLECT, 0);
|
||||||
|
}, nf7::Env::Time {});
|
||||||
|
}
|
||||||
|
}
|
||||||
void LuaContext::UpdateTooltip() noexcept {
|
void LuaContext::UpdateTooltip() noexcept {
|
||||||
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
|
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
|
||||||
if (q_) {
|
if (q_) {
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include <ImNodes.h>
|
#include <ImNodes.h>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/std/string.hpp>
|
#include <yas/types/std/string.hpp>
|
||||||
|
|
||||||
@ -17,9 +19,9 @@
|
|||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
|
#include "common/gui_config.hh"
|
||||||
#include "common/gui_file.hh"
|
#include "common/gui_file.hh"
|
||||||
#include "common/gui_node.hh"
|
#include "common/gui_node.hh"
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/luajit_queue.hh"
|
#include "common/luajit_queue.hh"
|
||||||
@ -28,6 +30,7 @@
|
|||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
|
#include "common/util_algorithm.hh"
|
||||||
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
@ -41,15 +44,15 @@ class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::
|
|||||||
static inline const nf7::GenericTypeInfo<InlineNode> kType =
|
static inline const nf7::GenericTypeInfo<InlineNode> kType =
|
||||||
{"LuaJIT/InlineNode", {"nf7::DirItem", "nf7::Node"}};
|
{"LuaJIT/InlineNode", {"nf7::DirItem", "nf7::Node"}};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
static void UpdateTypeTooltip() noexcept {
|
||||||
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
|
ImGui::TextUnformatted("Defines new pure Node without creating nfile.");
|
||||||
ImGui::Bullet();
|
|
||||||
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
Data() noexcept { }
|
Data() noexcept { }
|
||||||
|
std::string Stringify() const noexcept;
|
||||||
|
void Parse(const std::string&);
|
||||||
|
|
||||||
std::string script;
|
std::string script;
|
||||||
std::vector<std::string> inputs = {"in"};
|
std::vector<std::string> inputs = {"in"};
|
||||||
@ -57,41 +60,43 @@ class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::
|
|||||||
};
|
};
|
||||||
|
|
||||||
InlineNode(nf7::Env& env, Data&& data = {}) noexcept :
|
InlineNode(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&socket_popup_}),
|
nf7::FileBase(kType, env, {}),
|
||||||
nf7::DirItem(nf7::DirItem::kWidget),
|
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||||
nf7::Node(nf7::Node::kCustomNode),
|
nf7::Node(nf7::Node::kCustomNode),
|
||||||
life_(*this),
|
life_(*this),
|
||||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||||
mem_(std::move(data), *this) {
|
mem_(std::move(data), *this) {
|
||||||
nf7::FileBase::Install(*log_);
|
nf7::FileBase::Install(*log_);
|
||||||
|
|
||||||
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
|
mem_.onCommit = mem_.onRestore = [this]() {
|
||||||
this->data().inputs = std::move(i);
|
cache_ = std::nullopt;
|
||||||
this->data().outputs = std::move(o);
|
|
||||||
mem_.Commit();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
InlineNode(nf7::Deserializer& ar) : InlineNode(ar.env()) {
|
InlineNode(nf7::Deserializer& ar) : InlineNode(ar.env()) {
|
||||||
ar(data().script, data().inputs, data().outputs);
|
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||||
|
nf7::util::Uniq(mem_->inputs);
|
||||||
|
nf7::util::Uniq(mem_->outputs);
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(data().script, data().inputs, data().outputs);
|
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<InlineNode>(env, Data {data()});
|
return std::make_unique<InlineNode>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
|
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
std::span<const std::string> GetInputs() const noexcept override {
|
||||||
return data().inputs;
|
return mem_->inputs;
|
||||||
}
|
}
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
std::span<const std::string> GetOutputs() const noexcept override {
|
||||||
return data().outputs;
|
return mem_->outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
|
||||||
|
|
||||||
void UpdateMenu() noexcept override;
|
void UpdateMenu() noexcept override;
|
||||||
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
||||||
void UpdateWidget() noexcept override;
|
void UpdateWidget() noexcept override;
|
||||||
@ -107,121 +112,107 @@ class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::
|
|||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
std::shared_ptr<nf7::LoggerRef> log_;
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
|
|
||||||
nf7::gui::IOSocketListPopup socket_popup_;
|
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class InlineNode::Lambda final : public nf7::Node::Lambda,
|
class InlineNode::Lambda final : public nf7::Node::Lambda,
|
||||||
public std::enable_shared_from_this<InlineNode::Lambda> {
|
public std::enable_shared_from_this<InlineNode::Lambda> {
|
||||||
public:
|
public:
|
||||||
Lambda(InlineNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
Lambda(InlineNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||||
nf7::Node::Lambda(f, parent), file_(f.life_), log_(f.log_) {
|
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view k, const nf7::Value& v,
|
void Handle(std::string_view k, const nf7::Value& v,
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
||||||
try {
|
try {
|
||||||
file_.EnforceAlive();
|
f_.EnforceAlive();
|
||||||
|
|
||||||
auto ljq = file_->
|
|
||||||
ResolveUpwardOrThrow("_luajit").
|
|
||||||
interfaceOrThrow<nf7::luajit::Queue>().self();
|
|
||||||
|
|
||||||
std::optional<std::string> scr;
|
|
||||||
|
|
||||||
auto& mem = file_->mem_;
|
|
||||||
if (last_ != std::exchange(last_, mem.Save()->id())) {
|
|
||||||
scr = mem.last().script;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
auto th = std::make_shared<nf7::luajit::Thread>(
|
f_->Build().
|
||||||
self, ljq,
|
ThenIf(self, [this, k = std::string {k}, v, caller](auto& func) mutable {
|
||||||
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
|
if (f_) StartThread(std::move(k), v, func, caller);
|
||||||
th->Install(log_);
|
}).
|
||||||
th_.emplace_back(th);
|
Catch<nf7::Exception>([log = log_](auto&) {
|
||||||
|
log->Warn("skips execution because of build failure");
|
||||||
auto ctx = std::make_shared<nf7::GenericContext>(*file_);
|
|
||||||
|
|
||||||
auto p = std::make_pair(std::string {k}, std::move(v));
|
|
||||||
ljq->Push(self, [this, ctx, ljq, caller, th, scr = std::move(scr), p = std::move(p)](auto L) {
|
|
||||||
auto thL = th->Init(L);
|
|
||||||
|
|
||||||
// push function
|
|
||||||
if (scr) {
|
|
||||||
if (0 != luaL_loadstring(thL, scr->c_str())) {
|
|
||||||
log_->Error("luajit parse error: "s+lua_tostring(thL, -1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lua_pushvalue(thL, -1);
|
|
||||||
func_.emplace(ctx, ljq, thL);
|
|
||||||
} else {
|
|
||||||
if (!func_) {
|
|
||||||
log_->Error("last cache is broken");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
func_->PushSelf(thL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// push args
|
|
||||||
lua_pushstring(thL, p.first.c_str()); // key
|
|
||||||
nf7::luajit::PushValue(thL, p.second); // value
|
|
||||||
|
|
||||||
// push ctx table
|
|
||||||
if (ctxtable_ && ctxtable_->ljq() != ljq) {
|
|
||||||
ctxtable_ = std::nullopt;
|
|
||||||
log_->Warn("LuaJIT queue changed, ctxtable is cleared");
|
|
||||||
}
|
|
||||||
if (ctxtable_) {
|
|
||||||
ctxtable_->PushSelf(thL);
|
|
||||||
} else {
|
|
||||||
lua_createtable(thL, 0, 0);
|
|
||||||
lua_pushvalue(thL, -1);
|
|
||||||
ctxtable_.emplace(ctx, ljq, thL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start function
|
|
||||||
th->Resume(thL, 3);
|
|
||||||
});
|
});
|
||||||
|
} catch (nf7::ExpiredException&) {
|
||||||
} catch (nf7::LifeExpiredException&) {
|
|
||||||
} catch (nf7::Exception& e) {
|
|
||||||
log_->Error(e.msg());
|
|
||||||
}
|
|
||||||
void Abort() noexcept override {
|
|
||||||
for (auto& wth : th_) {
|
|
||||||
if (auto th = wth.lock()) {
|
|
||||||
th->Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// synchronized with filesystem
|
nf7::Life<InlineNode>::Ref f_;
|
||||||
nf7::Life<InlineNode>::Ref file_;
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
std::shared_ptr<nf7::LoggerRef> log_;
|
||||||
|
|
||||||
std::optional<nf7::Memento::Tag::Id> last_;
|
std::mutex mtx_;
|
||||||
|
std::optional<nf7::luajit::Ref> ctx_;
|
||||||
|
|
||||||
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
|
|
||||||
|
|
||||||
// used on luajit thread
|
void StartThread(std::string&& k, const nf7::Value& v,
|
||||||
std::optional<nf7::luajit::Ref> func_;
|
const std::shared_ptr<nf7::luajit::Ref>& func,
|
||||||
std::optional<nf7::luajit::Ref> ctxtable_;
|
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept {
|
||||||
|
auto ljq = func->ljq();
|
||||||
|
auto self = shared_from_this();
|
||||||
|
|
||||||
|
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(caller, self);
|
||||||
|
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl));
|
||||||
|
th->Install(log_);
|
||||||
|
|
||||||
|
ljq->Push(self, [this, ljq, th, func, k = std::move(k), v, caller](auto L) mutable {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> k {mtx_};
|
||||||
|
if (!ctx_ || ctx_->ljq() != ljq) {
|
||||||
|
lua_createtable(L, 0, 0);
|
||||||
|
ctx_.emplace(shared_from_this(), ljq, L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
L = th->Init(L);
|
||||||
|
func->PushSelf(L);
|
||||||
|
nf7::luajit::PushAll(L, k, v);
|
||||||
|
ctx_->PushSelf(L);
|
||||||
|
th->Resume(L, 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> InlineNode::CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> InlineNode::CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||||
return std::make_shared<Lambda>(*this, parent);
|
return std::make_shared<Lambda>(*this, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> InlineNode::Build() noexcept
|
||||||
|
try {
|
||||||
|
if (cache_) return *cache_;
|
||||||
|
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(*this, "inline function builder");
|
||||||
|
auto ljq =
|
||||||
|
ResolveUpwardOrThrow("_luajit").
|
||||||
|
interfaceOrThrow<nf7::luajit::Queue>().self();
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
|
||||||
|
ljq->Push(ctx, [ctx, ljq, pro, script = mem_->script](auto L) mutable {
|
||||||
|
if (0 == luaL_loadstring(L, script.c_str())) {
|
||||||
|
pro.Return(std::make_shared<nf7::luajit::Ref>(ctx, ljq, L));
|
||||||
|
} else {
|
||||||
|
pro.Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cache_ = pro.future().
|
||||||
|
Catch<nf7::Exception>([log = log_](auto& e) {
|
||||||
|
log->Error(e);
|
||||||
|
});
|
||||||
|
return *cache_;
|
||||||
|
} catch (nf7::Exception&) {
|
||||||
|
return {std::current_exception()};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void InlineNode::UpdateMenu() noexcept {
|
void InlineNode::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("I/O list")) {
|
if (ImGui::BeginMenu("config")) {
|
||||||
socket_popup_.Open(data().inputs, data().outputs);
|
nf7::gui::Config(mem_);
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
|
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||||
@ -229,32 +220,66 @@ void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
|
|||||||
|
|
||||||
ImGui::TextUnformatted("LuaJIT/InlineNode");
|
ImGui::TextUnformatted("LuaJIT/InlineNode");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::SmallButton("I/O list")) {
|
if (ImGui::SmallButton("config")) {
|
||||||
socket_popup_.Open(data().inputs, data().outputs);
|
ImGui::OpenPopup("ConfigPopup");
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("ConfigPopup")) {
|
||||||
|
nf7::gui::Config(mem_);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("build")) {
|
||||||
|
Build();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("try to compile the script (for syntax check)");
|
||||||
}
|
}
|
||||||
|
|
||||||
nf7::gui::NodeInputSockets(data().inputs);
|
nf7::gui::NodeInputSockets(mem_->inputs);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::InputTextMultiline("##script", &data().script, {24*em, 8*em});
|
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
mem_.Commit();
|
mem_.Commit();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
nf7::gui::NodeOutputSockets(data().outputs);
|
nf7::gui::NodeOutputSockets(mem_->outputs);
|
||||||
|
|
||||||
socket_popup_.Update();
|
|
||||||
}
|
}
|
||||||
void InlineNode::UpdateWidget() noexcept {
|
void InlineNode::UpdateWidget() noexcept {
|
||||||
ImGui::TextUnformatted("LuaJIT/InlineNode");
|
nf7::gui::Config(mem_);
|
||||||
if (ImGui::Button("I/O list")) {
|
}
|
||||||
socket_popup_.Open(data().inputs, data().outputs);
|
|
||||||
|
|
||||||
|
std::string InlineNode::Data::Stringify() const noexcept {
|
||||||
|
YAML::Emitter st;
|
||||||
|
st << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "inputs";
|
||||||
|
st << YAML::Value << inputs;
|
||||||
|
st << YAML::Key << "outputs";
|
||||||
|
st << YAML::Value << outputs;
|
||||||
|
st << YAML::Key << "script";
|
||||||
|
st << YAML::Value << YAML::Literal << script;
|
||||||
|
st << YAML::EndMap;
|
||||||
|
return std::string {st.c_str(), st.size()};
|
||||||
|
}
|
||||||
|
void InlineNode::Data::Parse(const std::string& str)
|
||||||
|
try {
|
||||||
|
const auto yaml = YAML::Load(str);
|
||||||
|
auto new_inputs = yaml["inputs"] .as<std::vector<std::string>>();
|
||||||
|
auto new_outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||||
|
auto new_script = yaml["script"].as<std::string>();
|
||||||
|
|
||||||
|
if (nf7::util::Uniq(new_inputs) > 0) {
|
||||||
|
throw nf7::Exception {"duplicated inputs"};
|
||||||
}
|
}
|
||||||
ImGui::InputTextMultiline("script", &data().script);
|
if (nf7::util::Uniq(new_outputs) > 0) {
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
throw nf7::Exception {"duplicated outputs"};
|
||||||
mem_.Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_popup_.Update();
|
inputs = std::move(new_inputs);
|
||||||
|
outputs = std::move(new_outputs);
|
||||||
|
script = std::move(new_script);
|
||||||
|
} catch (YAML::Exception& e) {
|
||||||
|
throw nf7::Exception {e.what()};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,117 +1,117 @@
|
|||||||
#include <algorithm>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <memory>
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <typeinfo>
|
#include <string>
|
||||||
#include <variant>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_stdlib.h>
|
#include <imgui_stdlib.h>
|
||||||
|
|
||||||
|
#include <lua.hpp>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include <yas/serialize.hpp>
|
#include <yas/serialize.hpp>
|
||||||
#include <yas/types/std/string.hpp>
|
|
||||||
#include <yas/types/std/vector.hpp>
|
|
||||||
|
|
||||||
#include "nf7.hh"
|
#include "nf7.hh"
|
||||||
|
|
||||||
#include "common/dir_item.hh"
|
#include "common/dir_item.hh"
|
||||||
#include "common/file_base.hh"
|
#include "common/file_base.hh"
|
||||||
#include "common/file_holder.hh"
|
|
||||||
#include "common/future.hh"
|
#include "common/future.hh"
|
||||||
#include "common/generic_context.hh"
|
#include "common/generic_context.hh"
|
||||||
#include "common/generic_type_info.hh"
|
#include "common/generic_type_info.hh"
|
||||||
#include "common/generic_memento.hh"
|
#include "common/generic_memento.hh"
|
||||||
#include "common/gui_file.hh"
|
#include "common/gui_config.hh"
|
||||||
#include "common/gui_popup.hh"
|
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
|
#include "common/luajit.hh"
|
||||||
#include "common/luajit_queue.hh"
|
#include "common/luajit_queue.hh"
|
||||||
#include "common/luajit_ref.hh"
|
#include "common/luajit_ref.hh"
|
||||||
#include "common/luajit_thread.hh"
|
#include "common/luajit_thread.hh"
|
||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
|
#include "common/nfile_watcher.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/node_root_lambda.hh"
|
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/util_string.hh"
|
#include "common/util_algorithm.hh"
|
||||||
|
#include "common/yas_std_filesystem.hh"
|
||||||
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
class LuaNode final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||||
public:
|
public:
|
||||||
static inline const GenericTypeInfo<Node> kType =
|
static inline const nf7::GenericTypeInfo<LuaNode> kType =
|
||||||
{"LuaJIT/Node", {"nf7::DirItem"}};
|
{"LuaJIT/Node", {"nf7::DirItem"}};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
static void UpdateTypeTooltip() noexcept {
|
||||||
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
|
ImGui::TextUnformatted("defines new pure Node");
|
||||||
ImGui::Bullet();
|
|
||||||
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Builder;
|
||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
|
struct Meta {
|
||||||
|
std::vector<std::string> inputs, outputs;
|
||||||
|
std::optional<nf7::luajit::Ref> lambda;
|
||||||
|
};
|
||||||
struct Data {
|
struct Data {
|
||||||
nf7::FileHolder::Tag obj;
|
std::string Stringify() const noexcept;
|
||||||
std::string desc;
|
void Parse(const std::string&);
|
||||||
std::vector<std::string> inputs;
|
|
||||||
std::vector<std::string> outputs;
|
std::filesystem::path npath;
|
||||||
};
|
};
|
||||||
|
|
||||||
Node(Env& env, Data&& data = {}) noexcept :
|
LuaNode(Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&obj_, &obj_editor_, &socket_popup_}),
|
nf7::FileBase(kType, env, {&nfile_watcher_}),
|
||||||
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
|
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
|
||||||
nf7::Node(nf7::Node::kNone),
|
nf7::Node(nf7::Node::kNone),
|
||||||
life_(*this),
|
life_(*this),
|
||||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||||
obj_(*this, "obj_factory", mem_),
|
|
||||||
obj_editor_(obj_, [](auto& t) { return t.flags().contains("nf7::Node"); }),
|
|
||||||
mem_(std::move(data), *this) {
|
mem_(std::move(data), *this) {
|
||||||
nf7::FileBase::Install(*log_);
|
nf7::FileBase::Install(*log_);
|
||||||
|
|
||||||
mem_.data().obj.SetTarget(obj_);
|
nfile_watcher_.onMod = [this]() {
|
||||||
mem_.CommitAmend();
|
cache_ = std::nullopt;
|
||||||
|
|
||||||
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
|
|
||||||
this->env().ExecMain(
|
|
||||||
std::make_shared<nf7::GenericContext>(*this),
|
|
||||||
[this, i = std::move(i), o = std::move(o)]() {
|
|
||||||
mem_.data().inputs = std::move(i);
|
|
||||||
mem_.data().outputs = std::move(o);
|
|
||||||
mem_.Commit();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
obj_.onEmplace = obj_.onChildUpdate = [this]() {
|
|
||||||
if (fu_) {
|
|
||||||
log_->Info("factory update detected, dropping cache");
|
|
||||||
}
|
|
||||||
fu_ = std::nullopt;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
LuaNode(nf7::Deserializer& ar) : LuaNode(ar.env()) {
|
||||||
ar(obj_, data().desc, data().inputs, data().outputs);
|
ar(mem_->npath);
|
||||||
|
|
||||||
nf7::util::Uniq(data().inputs);
|
|
||||||
nf7::util::Uniq(data().outputs);
|
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(obj_, data().desc, data().inputs, data().outputs);
|
ar(mem_->npath);
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<Node>(env, Data {data()});
|
return std::make_unique<LuaNode>(env, Data {mem_.data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
std::span<const std::string> GetInputs() const noexcept override {
|
std::span<const std::string> GetInputs() const noexcept override {
|
||||||
return data().inputs;
|
if (cache_ && cache_->done()) return cache_->value()->inputs;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
std::span<const std::string> GetOutputs() const noexcept override {
|
std::span<const std::string> GetOutputs() const noexcept override {
|
||||||
return data().outputs;
|
if (cache_ && cache_->done()) return cache_->value()->outputs;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
nf7::Future<std::shared_ptr<Meta>> Build() noexcept;
|
||||||
|
|
||||||
|
void Handle(const nf7::File::Event& ev) noexcept override {
|
||||||
|
nf7::FileBase::Handle(ev);
|
||||||
|
switch (ev.type) {
|
||||||
|
case nf7::File::Event::kAdd:
|
||||||
|
Build();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTooltip() noexcept override;
|
void UpdateTooltip() noexcept override;
|
||||||
@ -123,182 +123,196 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<Node> life_;
|
nf7::Life<LuaNode> life_;
|
||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
std::shared_ptr<nf7::LoggerRef> log_;
|
||||||
|
|
||||||
nf7::FileHolder obj_;
|
NFileWatcher nfile_watcher_;
|
||||||
nf7::gui::FileHolderEditor obj_editor_;
|
|
||||||
|
|
||||||
nf7::gui::IOSocketListPopup socket_popup_;
|
|
||||||
|
|
||||||
nf7::GenericMemento<Data> mem_;
|
nf7::GenericMemento<Data> mem_;
|
||||||
const Data& data() const noexcept { return mem_.data(); }
|
|
||||||
Data& data() noexcept { return mem_.data(); }
|
|
||||||
|
|
||||||
// factory context
|
std::optional<nf7::Future<std::shared_ptr<Meta>>> cache_;
|
||||||
std::shared_ptr<nf7::NodeRootLambda> factory_;
|
|
||||||
std::optional<nf7::Future<nf7::Value>> fu_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Node::Lambda final : public nf7::Node::Lambda,
|
class LuaNode::Lambda final : public nf7::Node::Lambda,
|
||||||
public std::enable_shared_from_this<Node::Lambda> {
|
public std::enable_shared_from_this<LuaNode::Lambda> {
|
||||||
public:
|
public:
|
||||||
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
Lambda(LuaNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||||
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle(std::string_view k, const nf7::Value& v,
|
void Handle(std::string_view k, const nf7::Value& v,
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
||||||
try {
|
try {
|
||||||
f_.EnforceAlive();
|
|
||||||
auto self = shared_from_this();
|
|
||||||
|
|
||||||
if (!f_->fu_) {
|
|
||||||
auto& n = f_->obj_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
|
|
||||||
auto b = nf7::NodeRootLambda::Builder {*f_, n};
|
|
||||||
f_->fu_ = b.Receive("product");
|
|
||||||
|
|
||||||
f_->factory_ = b.Build();
|
|
||||||
b.Send("create", nf7::Value::Pulse {});
|
|
||||||
|
|
||||||
f_->fu_->ThenSub(self, [this](auto) { if (f_) f_->factory_ = nullptr; });
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(f_->fu_);
|
|
||||||
f_->fu_->ThenSub(self, [this, k = std::string {k}, v = v, caller](auto fu) mutable {
|
|
||||||
try {
|
|
||||||
auto ref = fu.value().template data<nf7::luajit::Ref>();
|
|
||||||
CallFunc(ref, std::move(k), std::move(v), caller);
|
|
||||||
} catch (nf7::Exception& e) {
|
|
||||||
log_->Error("failed to call lua function: "+e.msg());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (nf7::LifeExpiredException&) {
|
|
||||||
} catch (nf7::Exception& e) {
|
|
||||||
log_->Error(e.msg());
|
|
||||||
}
|
|
||||||
void Abort() noexcept override {
|
|
||||||
for (auto& wth : th_) {
|
|
||||||
if (auto th = wth.lock()) {
|
|
||||||
th->Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
nf7::Life<Node>::Ref f_;
|
|
||||||
|
|
||||||
std::shared_ptr<nf7::LoggerRef> log_;
|
|
||||||
|
|
||||||
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
|
|
||||||
|
|
||||||
std::optional<nf7::luajit::Ref> ctxtable_;
|
|
||||||
|
|
||||||
|
|
||||||
void CallFunc(const std::shared_ptr<nf7::luajit::Ref>& func,
|
|
||||||
std::string&& k, nf7::Value&& v,
|
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& caller) {
|
|
||||||
auto self = shared_from_this();
|
|
||||||
th_.erase(
|
th_.erase(
|
||||||
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
|
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
|
||||||
th_.end());
|
th_.end());
|
||||||
|
|
||||||
auto ljq = func->ljq();
|
auto self = shared_from_this();
|
||||||
auto th = std::make_shared<nf7::luajit::Thread>(
|
|
||||||
self, ljq,
|
f_.EnforceAlive();
|
||||||
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
|
f_->Build().
|
||||||
th->Install(log_);
|
ThenIf(self, [this, k = std::string {k}, v, caller](auto& meta) mutable {
|
||||||
|
if (f_) StartThread(std::move(k), v, caller, meta);
|
||||||
|
}).
|
||||||
|
Catch<nf7::Exception>([log = f_->log_](auto& e) {
|
||||||
|
log->Error(e);
|
||||||
|
});
|
||||||
|
} catch (nf7::ExpiredException&) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Abort() noexcept override {
|
||||||
|
for (auto wth : th_) {
|
||||||
|
auto th = wth.lock();
|
||||||
|
th->Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::Life<LuaNode>::Ref f_;
|
||||||
|
|
||||||
|
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
|
||||||
|
|
||||||
|
std::mutex mtx_;
|
||||||
|
std::optional<nf7::luajit::Ref> ctx_;
|
||||||
|
|
||||||
|
|
||||||
|
void StartThread(std::string&& k, const nf7::Value& v,
|
||||||
|
const std::shared_ptr<nf7::Node::Lambda>& caller,
|
||||||
|
const std::shared_ptr<Meta>& meta) {
|
||||||
|
auto self = shared_from_this();
|
||||||
|
auto log = f_->log_;
|
||||||
|
auto ljq = meta->lambda->ljq();
|
||||||
|
|
||||||
|
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(caller, self);
|
||||||
|
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl));
|
||||||
|
th->Install(log);
|
||||||
th_.emplace_back(th);
|
th_.emplace_back(th);
|
||||||
|
|
||||||
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
|
ljq->Push(self, [this, ljq, th, meta, k = std::move(k), v](auto L) {
|
||||||
ljq->Push(self, [this, ctx, ljq, k = std::move(k), v = std::move(v), caller, func, th](auto L) mutable {
|
// create context table
|
||||||
auto thL = th->Init(L);
|
{
|
||||||
func->PushSelf(thL);
|
std::unique_lock<std::mutex> _(mtx_);
|
||||||
|
if (!ctx_ || ctx_->ljq() != ljq) {
|
||||||
// push args
|
lua_createtable(L, 0, 0);
|
||||||
lua_pushstring(thL, k.c_str());
|
ctx_.emplace(shared_from_this(), ljq, L);
|
||||||
nf7::luajit::PushValue(thL, v);
|
|
||||||
|
|
||||||
// push context table
|
|
||||||
if (ctxtable_ && ctxtable_->ljq() != ljq) {
|
|
||||||
ctxtable_ = std::nullopt;
|
|
||||||
}
|
}
|
||||||
if (!ctxtable_) {
|
|
||||||
lua_createtable(thL, 0, 0);
|
|
||||||
lua_pushvalue(thL, -1);
|
|
||||||
ctxtable_.emplace(ctx, ljq, thL);
|
|
||||||
} else {
|
|
||||||
ctxtable_->PushSelf(thL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute
|
// start thread
|
||||||
th->Resume(thL, 3);
|
L = th->Init(L);
|
||||||
|
meta->lambda->PushSelf(L);
|
||||||
|
nf7::luajit::PushAll(L, k, v);
|
||||||
|
ctx_->PushSelf(L);
|
||||||
|
th->Resume(L, 3);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
std::shared_ptr<nf7::Node::Lambda> LuaNode::CreateLambda(
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
|
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||||
return std::make_shared<Lambda>(*this, parent);
|
return std::make_shared<Lambda>(*this, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::UpdateTooltip() noexcept {
|
|
||||||
ImGui::Text("factory:");
|
|
||||||
ImGui::Indent();
|
|
||||||
obj_editor_.Tooltip();
|
|
||||||
ImGui::Unindent();
|
|
||||||
ImGui::Spacing();
|
|
||||||
|
|
||||||
ImGui::Text("input:");
|
nf7::Future<std::shared_ptr<LuaNode::Meta>> LuaNode::Build() noexcept {
|
||||||
ImGui::Indent();
|
if (cache_) return *cache_;
|
||||||
for (const auto& name : data().inputs) {
|
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
|
||||||
}
|
|
||||||
if (data().inputs.empty()) {
|
|
||||||
ImGui::TextDisabled("(nothing)");
|
|
||||||
}
|
|
||||||
ImGui::Unindent();
|
|
||||||
|
|
||||||
ImGui::Text("output:");
|
auto ctx = std::make_shared<nf7::GenericContext>(*this, "LuaJIT Node builder");
|
||||||
ImGui::Indent();
|
nf7::Future<std::shared_ptr<Meta>>::Promise pro {ctx};
|
||||||
for (const auto& name : data().outputs) {
|
try {
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
auto ljq =
|
||||||
}
|
ResolveUpwardOrThrow("_luajit").
|
||||||
if (data().outputs.empty()) {
|
interfaceOrThrow<nf7::luajit::Queue>().self();
|
||||||
ImGui::TextDisabled("(nothing)");
|
|
||||||
}
|
|
||||||
ImGui::Unindent();
|
|
||||||
|
|
||||||
ImGui::Text("description:");
|
auto handler = nf7::luajit::Thread::CreatePromiseHandler<std::shared_ptr<Meta>>(pro, [ctx, ljq](auto L) {
|
||||||
ImGui::Indent();
|
if (1 != lua_gettop(L) || !lua_istable(L, 1)) {
|
||||||
if (data().desc.empty()) {
|
throw nf7::Exception {"builder script should return a table"};
|
||||||
ImGui::TextDisabled("(empty)");
|
|
||||||
} else {
|
|
||||||
ImGui::TextUnformatted(data().desc.c_str());
|
|
||||||
}
|
}
|
||||||
ImGui::Unindent();
|
|
||||||
|
auto ret = std::make_shared<Meta>();
|
||||||
|
|
||||||
|
lua_getfield(L, 1, "inputs");
|
||||||
|
nf7::luajit::ToStringList(L, ret->inputs, -1);
|
||||||
|
if (nf7::util::Uniq(ret->inputs) > 0) {
|
||||||
|
throw nf7::Exception {"duplicated inputs"};
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, 1, "outputs");
|
||||||
|
nf7::luajit::ToStringList(L, ret->outputs, -1);
|
||||||
|
if (nf7::util::Uniq(ret->outputs)) {
|
||||||
|
throw nf7::Exception {"duplicated outputs"};
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, 1, "lambda");
|
||||||
|
ret->lambda.emplace(ctx, ljq, L);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto th = std::make_shared<nf7::luajit::Thread>(ctx, ljq, std::move(handler));
|
||||||
|
th->Install(log_);
|
||||||
|
ljq->Push(ctx, [ljq, pro, th, npath = mem_->npath](auto L) mutable {
|
||||||
|
auto thL = th->Init(L);
|
||||||
|
|
||||||
|
const auto npathstr = npath.string();
|
||||||
|
const auto ret = luaL_loadfile(thL, npathstr.c_str());
|
||||||
|
switch (ret) {
|
||||||
|
case 0:
|
||||||
|
th->Resume(thL, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pro.Throw<nf7::Exception>(lua_tostring(thL, -1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (nf7::Exception&) {
|
||||||
|
pro.Throw(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_ = pro.future().
|
||||||
|
Catch<nf7::Exception>(ctx, [log = log_](auto& e) {
|
||||||
|
log->Error(e);
|
||||||
|
});
|
||||||
|
return *cache_;
|
||||||
}
|
}
|
||||||
void Node::UpdateWidget() noexcept {
|
|
||||||
const auto em = ImGui::GetFontSize();
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("LuaJIT/Node: config");
|
|
||||||
obj_editor_.ButtonWithLabel("obj factory");
|
|
||||||
|
|
||||||
ImGui::InputTextMultiline("description", &data().desc, {0, 4*em});
|
void LuaNode::UpdateTooltip() noexcept {
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
ImGui::Text("cache : %s", cache_? "ready": "none");
|
||||||
mem_.Commit();
|
|
||||||
|
if (cache_ && cache_->done()) {
|
||||||
|
auto cache = cache_->value();
|
||||||
|
ImGui::TextUnformatted("inputs:");
|
||||||
|
for (const auto& name : cache->inputs) {
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
||||||
}
|
}
|
||||||
|
ImGui::TextUnformatted("outputs:");
|
||||||
if (ImGui::Button("I/O list")) {
|
for (const auto& name : cache->outputs) {
|
||||||
socket_popup_.Open(data().inputs, data().outputs);
|
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void LuaNode::UpdateWidget() noexcept {
|
||||||
|
nf7::gui::Config(mem_);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
|
||||||
obj_editor_.ItemWidget("obj factory");
|
|
||||||
|
|
||||||
socket_popup_.Update();
|
std::string LuaNode::Data::Stringify() const noexcept {
|
||||||
|
YAML::Emitter st;
|
||||||
|
st << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "npath";
|
||||||
|
st << YAML::Value << npath.string();
|
||||||
|
st << YAML::EndMap;
|
||||||
|
return std::string {st.c_str(), st.size()};
|
||||||
|
}
|
||||||
|
void LuaNode::Data::Parse(const std::string& str)
|
||||||
|
try {
|
||||||
|
const auto yaml = YAML::Load(str);
|
||||||
|
npath = yaml["npath"].as<std::string>();
|
||||||
|
} catch (YAML::Exception& e) {
|
||||||
|
throw nf7::Exception {e.what()};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "common/node_link_store.hh"
|
#include "common/node_link_store.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/squashed_history.hh"
|
#include "common/squashed_history.hh"
|
||||||
|
#include "common/util_algorithm.hh"
|
||||||
#include "common/yas_imgui.hh"
|
#include "common/yas_imgui.hh"
|
||||||
#include "common/yas_imnodes.hh"
|
#include "common/yas_imnodes.hh"
|
||||||
#include "common/yas_nf7.hh"
|
#include "common/yas_nf7.hh"
|
||||||
|
@ -201,7 +201,7 @@ try {
|
|||||||
ssla_ = nullptr;
|
ssla_ = nullptr;
|
||||||
la_ = nullptr;
|
la_ = nullptr;
|
||||||
}
|
}
|
||||||
} catch (nf7::LifeExpiredException&) {
|
} catch (nf7::ExpiredException&) {
|
||||||
ss->Finish();
|
ss->Finish();
|
||||||
} catch (nf7::FileHolder::EmptyException&) {
|
} catch (nf7::FileHolder::EmptyException&) {
|
||||||
ss->Finish();
|
ss->Finish();
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include "common/gui_timeline.hh"
|
#include "common/gui_timeline.hh"
|
||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
|
#include "common/logger_ref.hh"
|
||||||
#include "common/memento.hh"
|
#include "common/memento.hh"
|
||||||
#include "common/memento_recorder.hh"
|
#include "common/memento_recorder.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
@ -71,10 +72,10 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
std::vector<std::unique_ptr<Layer>>&& layers = {},
|
||||||
ItemId next = 1,
|
ItemId next = 1,
|
||||||
const nf7::gui::Window* win = nullptr) noexcept :
|
const nf7::gui::Window* win = nullptr) noexcept :
|
||||||
nf7::FileBase(kType, env, {&popup_socket_, &popup_add_item_}),
|
nf7::FileBase(kType, env, {&log_, &popup_socket_, &popup_add_item_}),
|
||||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||||
nf7::Node(nf7::Node::kMenu_DirItem),
|
nf7::Node(nf7::Node::kMenu_DirItem),
|
||||||
life_(*this),
|
life_(*this), log_(*this),
|
||||||
layers_(std::move(layers)), next_(next),
|
layers_(std::move(layers)), next_(next),
|
||||||
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
win_(*this, "Timeline Editor", win), tl_("timeline"),
|
||||||
popup_add_item_(*this) {
|
popup_add_item_(*this) {
|
||||||
@ -124,6 +125,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<TL> life_;
|
nf7::Life<TL> life_;
|
||||||
|
nf7::LoggerRef log_;
|
||||||
|
|
||||||
nf7::SquashedHistory history_;
|
nf7::SquashedHistory history_;
|
||||||
|
|
||||||
@ -419,6 +421,14 @@ class TL::Layer final {
|
|||||||
return std::make_unique<TL::Layer>(std::move(items), enabled_, height_);
|
return std::make_unique<TL::Layer>(std::move(items), enabled_, height_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MoveItem don't update Item::layer() neither Item::displayLayer()
|
||||||
|
void MoveItemTo(TL::Item& item, TL::Layer& dst) noexcept {
|
||||||
|
dst.AddItem(RemoveItem(item));
|
||||||
|
}
|
||||||
|
void ReorderItem(TL::Item& item) noexcept {
|
||||||
|
AddItem(RemoveItem(item));
|
||||||
|
}
|
||||||
|
|
||||||
void Attach(TL& f, TL::Layer* prev, TL::Layer* next) noexcept {
|
void Attach(TL& f, TL::Layer* prev, TL::Layer* next) noexcept {
|
||||||
assert(!owner_);
|
assert(!owner_);
|
||||||
|
|
||||||
@ -436,19 +446,6 @@ class TL::Layer final {
|
|||||||
next_ = nullptr;
|
next_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even after this, the item refers previous layer.
|
|
||||||
// To replace to new one, call item.MoveTo().
|
|
||||||
void MoveItemTo(TL::Layer& target, TL::Item& item) noexcept {
|
|
||||||
auto itr = std::find_if(items_.begin(), items_.end(),
|
|
||||||
[&](auto& x) { return x.get() == &item; });
|
|
||||||
if (itr == items_.end()) return;
|
|
||||||
|
|
||||||
auto uptr = std::move(*itr);
|
|
||||||
items_.erase(itr);
|
|
||||||
|
|
||||||
target.items_.push_back(std::move(uptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
TL::Item* GetAt(uint64_t t) const noexcept {
|
TL::Item* GetAt(uint64_t t) const noexcept {
|
||||||
auto itr = std::find_if(
|
auto itr = std::find_if(
|
||||||
items_.begin(), items_.end(),
|
items_.begin(), items_.end(),
|
||||||
@ -542,6 +539,26 @@ class TL::Layer final {
|
|||||||
// GUI temporary parameters
|
// GUI temporary parameters
|
||||||
size_t index_;
|
size_t index_;
|
||||||
float offset_y_;
|
float offset_y_;
|
||||||
|
|
||||||
|
|
||||||
|
// Add/RemoveItem don't update item.layer() field.
|
||||||
|
// Use ItemSwapCommand or Item::MoveTo() to do it.
|
||||||
|
void AddItem(std::unique_ptr<TL::Item>&& item) noexcept {
|
||||||
|
const auto border = item->timing().end();
|
||||||
|
auto itr = std::find_if(items_.begin(), items_.end(),
|
||||||
|
[&](auto& x) { return border <= x->timing().begin(); });
|
||||||
|
items_.insert(itr, std::move(item));
|
||||||
|
}
|
||||||
|
std::unique_ptr<TL::Item> RemoveItem(TL::Item& item) noexcept {
|
||||||
|
auto itr = std::find_if(items_.begin(), items_.end(),
|
||||||
|
[&](auto& x) { return x.get() == &item; });
|
||||||
|
if (itr == items_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto uptr = std::move(*itr);
|
||||||
|
items_.erase(itr);
|
||||||
|
return uptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
void TL::AssignId() {
|
void TL::AssignId() {
|
||||||
next_ = 1;
|
next_ = 1;
|
||||||
@ -765,16 +782,18 @@ class TL::Session final : public Sequencer::Session,
|
|||||||
static_cast<nf7::Value::Scalar>(t.dur());
|
static_cast<nf7::Value::Scalar>(t.dur());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void TL::Lambda::Handle(std::string_view name, const nf7::Value& v,
|
void TL::Lambda::Handle(std::string_view name, const nf7::Value& v,
|
||||||
const std::shared_ptr<Node::Lambda>&) noexcept {
|
const std::shared_ptr<Node::Lambda>&) noexcept {
|
||||||
if (name == "_exec") {
|
if (name == "_exec") {
|
||||||
if (!owner_) return;
|
if (!owner_) return;
|
||||||
|
|
||||||
uint64_t t;
|
uint64_t t;
|
||||||
if (v.isInteger()) {
|
if (v.isInteger()) {
|
||||||
const auto ti = std::max(v.integer(), int64_t{0});
|
const auto ti = std::max(v.integer(), int64_t{0});
|
||||||
t = static_cast<uint64_t>(ti);
|
t = static_cast<uint64_t>(ti);
|
||||||
} else {
|
} else {
|
||||||
// TODO: error
|
owner_->log_.Error("_exec takes a frame index");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CreateSession(t)->StartNext();
|
CreateSession(t)->StartNext();
|
||||||
@ -805,7 +824,6 @@ class TL::Editor final : public nf7::Sequencer::Editor {
|
|||||||
public:
|
public:
|
||||||
Editor(TL::Item& item) noexcept : item_(&item) {
|
Editor(TL::Item& item) noexcept : item_(&item) {
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TL::Item* const item_;
|
TL::Item* const item_;
|
||||||
@ -936,28 +954,15 @@ class TL::Layer::ItemSwapCommand final : public nf7::History::Command {
|
|||||||
TL::Item* const ptr_;
|
TL::Item* const ptr_;
|
||||||
|
|
||||||
void Swap() {
|
void Swap() {
|
||||||
auto& items = layer_->items_;
|
|
||||||
if (item_) {
|
if (item_) {
|
||||||
const auto& t = item_->timing();
|
|
||||||
auto itr = std::find_if(
|
|
||||||
items.begin(), items.end(),
|
|
||||||
[t = t.begin()](auto& x) { return t <= x->timing().begin(); });
|
|
||||||
if (itr != items.end()) {
|
|
||||||
if (t.end() > (*itr)->timing().begin()) {
|
|
||||||
throw nf7::History::CorruptException {"timing overlap"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item_->Attach(*layer_->owner_, *layer_);
|
item_->Attach(*layer_->owner_, *layer_);
|
||||||
items.insert(itr, std::move(item_));
|
layer_->AddItem(std::move(item_));
|
||||||
} else {
|
} else {
|
||||||
auto itr = std::find_if(items.begin(), items.end(),
|
item_ = layer_->RemoveItem(*ptr_);
|
||||||
[ptr = ptr_](auto& x) { return x.get() == ptr; });
|
if (!item_) {
|
||||||
if (itr == items.end()) {
|
|
||||||
throw nf7::History::CorruptException {"target item missing"};
|
throw nf7::History::CorruptException {"target item missing"};
|
||||||
}
|
}
|
||||||
item_ = std::move(*itr);
|
|
||||||
item_->Detach();
|
item_->Detach();
|
||||||
items.erase(itr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -983,8 +988,7 @@ class TL::Layer::ItemTimingSwapCommand final : public nf7::History::Command {
|
|||||||
void Exec() noexcept {
|
void Exec() noexcept {
|
||||||
std::swap(item_->timing(), timing_);
|
std::swap(item_->timing(), timing_);
|
||||||
item_->displayTiming() = item_->timing();
|
item_->displayTiming() = item_->timing();
|
||||||
|
item_->layer().ReorderItem(*item_);
|
||||||
// TODO: reorder item
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void TL::ExecApplyTimingOfSelected() noexcept {
|
void TL::ExecApplyTimingOfSelected() noexcept {
|
||||||
@ -1070,11 +1074,11 @@ class TL::Layer::ItemMoveCommand final : public nf7::History::Command {
|
|||||||
src_(&src), dst_(&dst), item_(&item) {
|
src_(&src), dst_(&dst), item_(&item) {
|
||||||
}
|
}
|
||||||
void Apply() noexcept override {
|
void Apply() noexcept override {
|
||||||
src_->MoveItemTo(*dst_, *item_);
|
dst_->AddItem(src_->RemoveItem(*item_));
|
||||||
item_->MoveTo(*dst_);
|
item_->MoveTo(*dst_);
|
||||||
}
|
}
|
||||||
void Revert() noexcept override {
|
void Revert() noexcept override {
|
||||||
dst_->MoveItemTo(*src_, *item_);
|
src_->AddItem(dst_->RemoveItem(*item_));
|
||||||
item_->MoveTo(*src_);
|
item_->MoveTo(*src_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1124,8 +1128,11 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
|
|||||||
layers.emplace_back(item, &layer);
|
layers.emplace_back(item, &layer);
|
||||||
}
|
}
|
||||||
for (auto& p : layers) {
|
for (auto& p : layers) {
|
||||||
p.first->displayLayer().MoveItemTo(*p.second, *p.first);
|
auto& item = *p.first;
|
||||||
p.first->DisplayOn(*p.second);
|
auto& src = item.displayLayer();
|
||||||
|
auto& dst = *p.second;
|
||||||
|
src.MoveItemTo(item, dst);
|
||||||
|
item.DisplayOn(dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
@ -23,7 +22,8 @@
|
|||||||
#include "common/gui_window.hh"
|
#include "common/gui_window.hh"
|
||||||
#include "common/life.hh"
|
#include "common/life.hh"
|
||||||
#include "common/logger_ref.hh"
|
#include "common/logger_ref.hh"
|
||||||
#include "common/native_file.hh"
|
#include "common/mutex.hh"
|
||||||
|
#include "common/nfile.hh"
|
||||||
#include "common/node.hh"
|
#include "common/node.hh"
|
||||||
#include "common/ptr_selector.hh"
|
#include "common/ptr_selector.hh"
|
||||||
#include "common/thread.hh"
|
#include "common/thread.hh"
|
||||||
@ -33,11 +33,11 @@
|
|||||||
namespace nf7 {
|
namespace nf7 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class NativeFile final : public nf7::FileBase,
|
class NFile final : public nf7::FileBase,
|
||||||
public nf7::DirItem, public nf7::Node {
|
public nf7::DirItem, public nf7::Node {
|
||||||
public:
|
public:
|
||||||
static inline const nf7::GenericTypeInfo<NativeFile> kType = {
|
static inline const nf7::GenericTypeInfo<NFile> kType = {
|
||||||
"System/NativeFile", {"nf7::DirItem", "nf7::Node"}};
|
"System/NFile", {"nf7::DirItem", "nf7::Node"}};
|
||||||
static void UpdateTypeTooltip() noexcept {
|
static void UpdateTypeTooltip() noexcept {
|
||||||
ImGui::TextUnformatted("Read/Write a file placed on native filesystem.");
|
ImGui::TextUnformatted("Read/Write a file placed on native filesystem.");
|
||||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
||||||
@ -46,30 +46,34 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
class Lambda;
|
class Lambda;
|
||||||
|
|
||||||
struct SharedData final {
|
struct SharedData final {
|
||||||
SharedData(NativeFile& f) noexcept : log(f) {
|
SharedData(NFile& f) noexcept : log(f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
nf7::LoggerRef log;
|
nf7::LoggerRef log;
|
||||||
std::optional<nf7::NativeFile> nfile;
|
std::optional<nf7::NFile> nfile;
|
||||||
|
|
||||||
std::atomic<bool> locked = false;
|
|
||||||
};
|
};
|
||||||
struct Runner final {
|
struct Runner final {
|
||||||
struct Task {
|
struct Task {
|
||||||
std::shared_ptr<NativeFile::Lambda> callee;
|
std::shared_ptr<nf7::Node::Lambda> callee, caller;
|
||||||
std::shared_ptr<nf7::Node::Lambda> caller;
|
|
||||||
std::function<nf7::Value()> func;
|
std::function<nf7::Value()> func;
|
||||||
|
|
||||||
std::filesystem::path npath;
|
|
||||||
nf7::NativeFile::Flags flags;
|
|
||||||
|
|
||||||
std::function<void(const std::shared_ptr<SharedData>&)> preproc;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Runner(const std::shared_ptr<SharedData>& shared) noexcept :
|
Runner(const std::shared_ptr<SharedData>& shared) noexcept :
|
||||||
shared_(shared) {
|
shared_(shared) {
|
||||||
}
|
}
|
||||||
void operator()(Task&&) noexcept;
|
void operator()(Task&& t) noexcept
|
||||||
|
try {
|
||||||
|
auto callee = t.callee;
|
||||||
|
auto caller = t.caller;
|
||||||
|
auto ret = t.func();
|
||||||
|
if (callee && caller) {
|
||||||
|
callee->env().ExecSub(callee, [callee, caller, ret]() {
|
||||||
|
caller->Handle("result", ret, callee);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (nf7::Exception& e) {
|
||||||
|
shared_->log.Error("operation failure: "+e.msg());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<SharedData> shared_;
|
std::shared_ptr<SharedData> shared_;
|
||||||
@ -81,7 +85,7 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
std::string mode;
|
std::string mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
NativeFile(nf7::Env& env, Data&& data = {}) noexcept :
|
NFile(nf7::Env& env, Data&& data = {}) noexcept :
|
||||||
nf7::FileBase(kType, env, {&config_popup_}),
|
nf7::FileBase(kType, env, {&config_popup_}),
|
||||||
nf7::DirItem(nf7::DirItem::kMenu |
|
nf7::DirItem(nf7::DirItem::kMenu |
|
||||||
nf7::DirItem::kTooltip |
|
nf7::DirItem::kTooltip |
|
||||||
@ -94,18 +98,18 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
config_popup_(*this) {
|
config_popup_(*this) {
|
||||||
nf7::FileBase::Install(shared_->log);
|
nf7::FileBase::Install(shared_->log);
|
||||||
|
|
||||||
mem_.onRestore = [this]() { Refresh(); };
|
mtx_.onLock = [this]() { SetUp(); };
|
||||||
mem_.onCommit = [this]() { Refresh(); };
|
mtx_.onUnlock = [this]() { shared_->nfile.reset(); };
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeFile(nf7::Deserializer& ar) : NativeFile(ar.env()) {
|
NFile(nf7::Deserializer& ar) : NFile(ar.env()) {
|
||||||
ar(data().npath, data().mode);
|
ar(data().npath, data().mode);
|
||||||
}
|
}
|
||||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
ar(data().npath, data().mode);
|
ar(data().npath, data().mode);
|
||||||
}
|
}
|
||||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
return std::make_unique<NativeFile>(env, Data {data()});
|
return std::make_unique<NFile>(env, Data {data()});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
@ -130,10 +134,11 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<NativeFile> life_;
|
nf7::Life<NFile> life_;
|
||||||
|
|
||||||
std::shared_ptr<SharedData> shared_;
|
std::shared_ptr<SharedData> shared_;
|
||||||
std::shared_ptr<Thread> th_;
|
std::shared_ptr<Thread> th_;
|
||||||
|
nf7::Mutex mtx_;
|
||||||
|
|
||||||
std::filesystem::file_time_type lastmod_;
|
std::filesystem::file_time_type lastmod_;
|
||||||
|
|
||||||
@ -147,7 +152,7 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
struct ConfigPopup final :
|
struct ConfigPopup final :
|
||||||
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
public nf7::FileBase::Feature, private nf7::gui::Popup {
|
||||||
public:
|
public:
|
||||||
ConfigPopup(NativeFile& f) noexcept :
|
ConfigPopup(NFile& f) noexcept :
|
||||||
nf7::gui::Popup("ConfigPopup"), f_(&f) {
|
nf7::gui::Popup("ConfigPopup"), f_(&f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,24 +167,35 @@ class NativeFile final : public nf7::FileBase,
|
|||||||
void Update() noexcept override;
|
void Update() noexcept override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NativeFile* const f_;
|
NFile* const f_;
|
||||||
|
|
||||||
std::string npath_;
|
std::string npath_;
|
||||||
bool read_, write_;
|
bool read_, write_;
|
||||||
} config_popup_;
|
} config_popup_;
|
||||||
|
|
||||||
|
|
||||||
void Refresh() noexcept {
|
void SetUp() {
|
||||||
Runner::Task t;
|
const auto& mode = data().mode;
|
||||||
t.preproc = [](auto& shared) { shared->nfile = std::nullopt; };
|
nf7::NFile::Flags flags = 0;
|
||||||
th_->Push(std::make_shared<nf7::GenericContext>(*this), std::move(t));
|
if (std::string::npos != mode.find('r')) flags |= nf7::NFile::kRead;
|
||||||
|
if (std::string::npos != mode.find('w')) flags |= nf7::NFile::kWrite;
|
||||||
|
|
||||||
|
auto ctx = std::make_shared<nf7::GenericContext>(*this);
|
||||||
|
th_->Push(ctx, Runner::Task {
|
||||||
|
.callee = nullptr,
|
||||||
|
.caller = nullptr,
|
||||||
|
.func = [shared = shared_, npath = data().npath, flags]() {
|
||||||
|
shared->nfile.emplace(npath, flags);
|
||||||
|
return nf7::Value::Pulse {};
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class NativeFile::Lambda final : public nf7::Node::Lambda,
|
class NFile::Lambda final : public nf7::Node::Lambda,
|
||||||
public std::enable_shared_from_this<NativeFile::Lambda> {
|
public std::enable_shared_from_this<NFile::Lambda> {
|
||||||
public:
|
public:
|
||||||
Lambda(NativeFile& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
Lambda(NFile& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||||
nf7::Node::Lambda(f, parent), f_(f.life_), shared_(f.shared_) {
|
nf7::Node::Lambda(f, parent), f_(f.life_), shared_(f.shared_) {
|
||||||
}
|
}
|
||||||
~Lambda() noexcept {
|
~Lambda() noexcept {
|
||||||
@ -192,20 +208,15 @@ class NativeFile::Lambda final : public nf7::Node::Lambda,
|
|||||||
|
|
||||||
const auto type = v.tuple("type").string();
|
const auto type = v.tuple("type").string();
|
||||||
if (type == "lock") {
|
if (type == "lock") {
|
||||||
Push(caller, [this]() {
|
const auto ex = v.tuple("ex").boolean();
|
||||||
Lock();
|
Push(caller, ex, []() { return nf7::Value::Pulse {}; });
|
||||||
return nf7::Value::Pulse {};
|
|
||||||
});
|
|
||||||
} else if (type == "unlock") {
|
} else if (type == "unlock") {
|
||||||
Push(caller, [this]() {
|
lock_ = std::nullopt;
|
||||||
shared_->nfile = std::nullopt;
|
caller->Handle("result", nf7::Value::Pulse {}, shared_from_this());
|
||||||
Unlock();
|
|
||||||
return nf7::Value::Pulse {};
|
|
||||||
});
|
|
||||||
} else if (type == "read") {
|
} else if (type == "read") {
|
||||||
const auto offset = v.tuple("offset").integer<size_t>();
|
const auto offset = v.tuple("offset").integer<size_t>();
|
||||||
const auto size = v.tuple("size").integer<size_t>();
|
const auto size = v.tuple("size").integer<size_t>();
|
||||||
Push(caller, [this, offset, size]() {
|
Push(caller, false, [this, offset, size]() {
|
||||||
std::vector<uint8_t> buf;
|
std::vector<uint8_t> buf;
|
||||||
buf.resize(size);
|
buf.resize(size);
|
||||||
const auto actual = shared_->nfile->Read(offset, buf.data(), size);
|
const auto actual = shared_->nfile->Read(offset, buf.data(), size);
|
||||||
@ -215,13 +226,13 @@ class NativeFile::Lambda final : public nf7::Node::Lambda,
|
|||||||
} else if (type == "write") {
|
} else if (type == "write") {
|
||||||
const auto offset = v.tuple("offset").integer<size_t>();
|
const auto offset = v.tuple("offset").integer<size_t>();
|
||||||
const auto buf = v.tuple("buf").vector();
|
const auto buf = v.tuple("buf").vector();
|
||||||
Push(caller, [this, offset, buf]() {
|
Push(caller, true, [this, offset, buf]() {
|
||||||
const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size());
|
const auto ret = shared_->nfile->Write(offset, buf->data(), buf->size());
|
||||||
return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
|
return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
|
||||||
});
|
});
|
||||||
} else if (type == "truncate") {
|
} else if (type == "truncate") {
|
||||||
const auto size = v.tuple("size").integer<size_t>();
|
const auto size = v.tuple("size").integer<size_t>();
|
||||||
Push(caller, [this, size]() {
|
Push(caller, true, [this, size]() {
|
||||||
shared_->nfile->Truncate(size);
|
shared_->nfile->Truncate(size);
|
||||||
return nf7::Value::Pulse {};
|
return nf7::Value::Pulse {};
|
||||||
});
|
});
|
||||||
@ -232,73 +243,35 @@ class NativeFile::Lambda final : public nf7::Node::Lambda,
|
|||||||
shared_->log.Error(e.msg());
|
shared_->log.Error(e.msg());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lock() {
|
|
||||||
if (!std::exchange(own_lock_, true)) {
|
|
||||||
if (shared_->locked.exchange(true)) {
|
|
||||||
throw nf7::Exception {"resource is busy"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Unlock() noexcept {
|
|
||||||
if (std::exchange(own_lock_, false)) {
|
|
||||||
assert(shared_->locked);
|
|
||||||
shared_->locked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ownLock() const noexcept { return own_lock_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nf7::Life<NativeFile>::Ref f_;
|
nf7::Life<NFile>::Ref f_;
|
||||||
|
|
||||||
std::shared_ptr<SharedData> shared_;
|
std::shared_ptr<SharedData> shared_;
|
||||||
|
|
||||||
bool own_lock_ = false;
|
std::optional<nf7::Future<std::shared_ptr<nf7::Mutex::Lock>>> lock_;
|
||||||
|
|
||||||
void Push(const std::shared_ptr<nf7::Node::Lambda>& caller, auto&& f) noexcept {
|
|
||||||
const auto& mode = f_->data().mode;
|
|
||||||
nf7::NativeFile::Flags flags = 0;
|
|
||||||
if (std::string::npos != mode.find('r')) flags |= nf7::NativeFile::kRead;
|
|
||||||
if (std::string::npos != mode.find('w')) flags |= nf7::NativeFile::kWrite;
|
|
||||||
|
|
||||||
|
void Push(const std::shared_ptr<nf7::Node::Lambda>& caller, bool ex, auto&& f) noexcept {
|
||||||
|
if (!lock_) {
|
||||||
|
lock_ = f_->mtx_.AcquireLock(ex);
|
||||||
|
}
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
f_->th_->Push(self, NativeFile::Runner::Task {
|
lock_->ThenIf([self, this, caller, f = std::move(f)](auto&) mutable {
|
||||||
|
f_->th_->Push(self, NFile::Runner::Task {
|
||||||
.callee = self,
|
.callee = self,
|
||||||
.caller = caller,
|
.caller = std::move(caller),
|
||||||
.func = std::move(f),
|
.func = std::move(f),
|
||||||
.npath = f_->data().npath,
|
});
|
||||||
.flags = flags,
|
|
||||||
.preproc = {},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
std::shared_ptr<nf7::Node::Lambda> NativeFile::CreateLambda(
|
std::shared_ptr<nf7::Node::Lambda> NFile::CreateLambda(
|
||||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||||
return std::make_shared<NativeFile::Lambda>(*this, parent);
|
return std::make_shared<NFile::Lambda>(*this, parent);
|
||||||
}
|
|
||||||
void NativeFile::Runner::operator()(Task&& t) noexcept
|
|
||||||
try {
|
|
||||||
if (t.preproc) {
|
|
||||||
t.preproc(shared_);
|
|
||||||
}
|
|
||||||
auto callee = t.callee;
|
|
||||||
auto caller = t.caller;
|
|
||||||
if (callee && caller) {
|
|
||||||
callee->Lock();
|
|
||||||
if (!shared_->nfile) {
|
|
||||||
shared_->nfile.emplace(callee->env(), callee->initiator(), t.npath, t.flags);
|
|
||||||
}
|
|
||||||
auto ret = t.func();
|
|
||||||
callee->env().ExecSub(callee, [callee, caller, ret = std::move(ret)]() {
|
|
||||||
caller->Handle("result", ret, callee);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (nf7::Exception& e) {
|
|
||||||
shared_->log.Error("operation failure: "+e.msg());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void NativeFile::Update() noexcept {
|
void NFile::Update() noexcept {
|
||||||
nf7::FileBase::Update();
|
nf7::FileBase::Update();
|
||||||
|
|
||||||
// file update check
|
// file update check
|
||||||
@ -311,24 +284,24 @@ void NativeFile::Update() noexcept {
|
|||||||
} catch (std::filesystem::filesystem_error&) {
|
} catch (std::filesystem::filesystem_error&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void NativeFile::UpdateMenu() noexcept {
|
void NFile::UpdateMenu() noexcept {
|
||||||
if (ImGui::MenuItem("config")) {
|
if (ImGui::MenuItem("config")) {
|
||||||
config_popup_.Open();
|
config_popup_.Open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void NativeFile::UpdateTooltip() noexcept {
|
void NFile::UpdateTooltip() noexcept {
|
||||||
ImGui::Text("npath: %s", data().npath.generic_string().c_str());
|
ImGui::Text("npath: %s", data().npath.generic_string().c_str());
|
||||||
ImGui::Text("mode : %s", data().mode.c_str());
|
ImGui::Text("mode : %s", data().mode.c_str());
|
||||||
}
|
}
|
||||||
void NativeFile::UpdateWidget() noexcept {
|
void NFile::UpdateWidget() noexcept {
|
||||||
ImGui::TextUnformatted("System/NativeFile");
|
ImGui::TextUnformatted("System/NFile");
|
||||||
|
|
||||||
if (ImGui::Button("config")) {
|
if (ImGui::Button("config")) {
|
||||||
config_popup_.Open();
|
config_popup_.Open();
|
||||||
}
|
}
|
||||||
config_popup_.Update();
|
config_popup_.Update();
|
||||||
}
|
}
|
||||||
void NativeFile::ConfigPopup::Update() noexcept {
|
void NFile::ConfigPopup::Update() noexcept {
|
||||||
if (nf7::gui::Popup::Begin()) {
|
if (nf7::gui::Popup::Begin()) {
|
||||||
ImGui::InputText("path", &npath_);
|
ImGui::InputText("path", &npath_);
|
||||||
ImGui::Checkbox("read", &read_);
|
ImGui::Checkbox("read", &read_);
|
472
file/value_plot.cc
Normal file
472
file/value_plot.cc
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_stdlib.h>
|
||||||
|
#include <implot.h>
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
|
#include <yas/serialize.hpp>
|
||||||
|
#include <yas/types/utility/usertype.hpp>
|
||||||
|
|
||||||
|
#include "nf7.hh"
|
||||||
|
|
||||||
|
#include "common/dir_item.hh"
|
||||||
|
#include "common/file_base.hh"
|
||||||
|
#include "common/generic_memento.hh"
|
||||||
|
#include "common/generic_type_info.hh"
|
||||||
|
#include "common/gui_config.hh"
|
||||||
|
#include "common/gui_node.hh"
|
||||||
|
#include "common/gui_window.hh"
|
||||||
|
#include "common/life.hh"
|
||||||
|
#include "common/logger_ref.hh"
|
||||||
|
#include "common/node.hh"
|
||||||
|
#include "common/ptr_selector.hh"
|
||||||
|
#include "common/util_algorithm.hh"
|
||||||
|
#include "common/util_string.hh"
|
||||||
|
#include "common/value.hh"
|
||||||
|
#include "common/yas_enum.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nf7 {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class Plot final : public nf7::FileBase,
|
||||||
|
public nf7::DirItem,
|
||||||
|
public nf7::Node {
|
||||||
|
public:
|
||||||
|
static inline const nf7::GenericTypeInfo<Plot> kType =
|
||||||
|
{"Value/Plot", {"nf7::DirItem", "nf7::Node"}};
|
||||||
|
static void UpdateTypeTooltip() noexcept {
|
||||||
|
ImGui::TextUnformatted("plotter");
|
||||||
|
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
||||||
|
}
|
||||||
|
|
||||||
|
class Lambda;
|
||||||
|
|
||||||
|
enum SeriesType {
|
||||||
|
kLine,
|
||||||
|
kScatter,
|
||||||
|
kBars,
|
||||||
|
};
|
||||||
|
enum SeriesFormat {
|
||||||
|
kU8 = 0x11,
|
||||||
|
kS8 = 0x21,
|
||||||
|
kU16 = 0x12,
|
||||||
|
kS16 = 0x22,
|
||||||
|
kU32 = 0x14,
|
||||||
|
kS32 = 0x24,
|
||||||
|
kF32 = 0x34,
|
||||||
|
kF64 = 0x38,
|
||||||
|
};
|
||||||
|
struct SeriesData {
|
||||||
|
SeriesFormat fmt;
|
||||||
|
|
||||||
|
nf7::Value::ConstVector xs;
|
||||||
|
nf7::Value::ConstVector ys;
|
||||||
|
|
||||||
|
double param[3];
|
||||||
|
size_t count = 0;
|
||||||
|
size_t offset = 0;
|
||||||
|
size_t stride = 0;
|
||||||
|
|
||||||
|
int flags;
|
||||||
|
};
|
||||||
|
struct Series {
|
||||||
|
std::string name;
|
||||||
|
SeriesType type;
|
||||||
|
SeriesFormat fmt;
|
||||||
|
|
||||||
|
std::shared_ptr<SeriesData> data;
|
||||||
|
|
||||||
|
Series(std::string_view n = "", SeriesType t = kLine, SeriesFormat f = kF32) noexcept :
|
||||||
|
name(n), type(t), fmt(f), data(std::make_shared<SeriesData>()) {
|
||||||
|
}
|
||||||
|
Series(const Series&) = default;
|
||||||
|
Series(Series&&) = default;
|
||||||
|
Series& operator=(const Series&) = default;
|
||||||
|
Series& operator=(Series&&) = default;
|
||||||
|
|
||||||
|
bool operator==(std::string_view v) const noexcept { return name == v; }
|
||||||
|
bool operator==(const Series& s) const noexcept { return name == s.name; }
|
||||||
|
void serialize(auto& ar) {
|
||||||
|
ar(name, type, fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update() const noexcept;
|
||||||
|
};
|
||||||
|
struct Data {
|
||||||
|
std::string Stringify() const noexcept;
|
||||||
|
void Parse(const std::string&);
|
||||||
|
|
||||||
|
std::vector<Series> series;
|
||||||
|
};
|
||||||
|
|
||||||
|
Plot(nf7::Env& env, const nf7::gui::Window* win = nullptr, Data&& data = {}) noexcept :
|
||||||
|
nf7::FileBase(kType, env, {&log_}),
|
||||||
|
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||||
|
nf7::Node(nf7::Node::kNone),
|
||||||
|
life_(*this), log_(*this), win_(*this, "Plot", win), mem_(std::move(data)) {
|
||||||
|
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
|
||||||
|
Sanitize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Plot(nf7::Deserializer& ar) : Plot(ar.env()) {
|
||||||
|
ar(win_, mem_->series);
|
||||||
|
Sanitize();
|
||||||
|
}
|
||||||
|
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||||
|
ar(win_, mem_->series);
|
||||||
|
}
|
||||||
|
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||||
|
return std::make_unique<Plot>(env, &win_, Data {mem_.data()});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||||
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||||
|
|
||||||
|
std::span<const std::string> GetInputs() const noexcept override {
|
||||||
|
return inputs_;
|
||||||
|
}
|
||||||
|
std::span<const std::string> GetOutputs() const noexcept override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update() noexcept override;
|
||||||
|
void UpdateWidget() noexcept override;
|
||||||
|
void UpdateMenu() noexcept override;
|
||||||
|
|
||||||
|
void UpdatePlot() noexcept;
|
||||||
|
|
||||||
|
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||||
|
return InterfaceSelector<
|
||||||
|
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::Life<Plot> life_;
|
||||||
|
nf7::LoggerRef log_;
|
||||||
|
|
||||||
|
nf7::gui::Window win_;
|
||||||
|
|
||||||
|
nf7::GenericMemento<Data> mem_;
|
||||||
|
|
||||||
|
std::vector<std::string> inputs_;
|
||||||
|
|
||||||
|
|
||||||
|
void Sanitize() {
|
||||||
|
nf7::util::Uniq(mem_->series);
|
||||||
|
mem_.CommitAmend();
|
||||||
|
}
|
||||||
|
void BuildInputList() {
|
||||||
|
inputs_.clear();
|
||||||
|
inputs_.reserve(mem_->series.size());
|
||||||
|
for (const auto& s : mem_->series) {
|
||||||
|
inputs_.push_back(s.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Plot::Lambda final : public nf7::Node::Lambda {
|
||||||
|
public:
|
||||||
|
Lambda(Plot& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||||
|
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Handle(std::string_view k, const nf7::Value& v,
|
||||||
|
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override
|
||||||
|
try {
|
||||||
|
f_.EnforceAlive();
|
||||||
|
|
||||||
|
const auto& series = f_->mem_->series;
|
||||||
|
auto itr = std::find(series.begin(), series.end(), k);
|
||||||
|
if (itr == series.end()) {
|
||||||
|
throw nf7::Exception {"unknown series name"};
|
||||||
|
}
|
||||||
|
const auto& s = *itr;
|
||||||
|
|
||||||
|
auto& data = *s.data;
|
||||||
|
if (v.isVector()) {
|
||||||
|
const auto& vec = v.vector();
|
||||||
|
const auto fmtsz = static_cast<size_t>(s.fmt & 0xF);
|
||||||
|
data = SeriesData {
|
||||||
|
.fmt = s.fmt,
|
||||||
|
.xs = vec,
|
||||||
|
.ys = nullptr,
|
||||||
|
.param = {0},
|
||||||
|
.count = vec->size() / fmtsz,
|
||||||
|
.offset = 0,
|
||||||
|
.stride = fmtsz,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
switch (s.type) {
|
||||||
|
case kLine:
|
||||||
|
case kScatter:
|
||||||
|
data.param[0] = 1; // xscale
|
||||||
|
break;
|
||||||
|
case kBars:
|
||||||
|
data.param[0] = 0.67; // barsize
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (v.isTuple()) {
|
||||||
|
// TODO: parameters
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw nf7::Exception {"expected vector"};
|
||||||
|
}
|
||||||
|
} catch (nf7::ExpiredException&) {
|
||||||
|
} catch (nf7::Exception& e) {
|
||||||
|
f_->log_.Warn("plotter error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nf7::Life<Plot>::Ref f_;
|
||||||
|
};
|
||||||
|
std::shared_ptr<nf7::Node::Lambda> Plot::CreateLambda(
|
||||||
|
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||||
|
return std::make_shared<Plot::Lambda>(*this, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Plot::Update() noexcept {
|
||||||
|
nf7::FileBase::Update();
|
||||||
|
|
||||||
|
if (win_.Begin()) {
|
||||||
|
UpdatePlot();
|
||||||
|
}
|
||||||
|
win_.End();
|
||||||
|
}
|
||||||
|
void Plot::UpdateWidget() noexcept {
|
||||||
|
if (ImGui::Button("plot window")) {
|
||||||
|
win_.SetFocus();
|
||||||
|
}
|
||||||
|
nf7::gui::Config(mem_);
|
||||||
|
}
|
||||||
|
void Plot::UpdateMenu() noexcept {
|
||||||
|
ImGui::MenuItem("plot window", nullptr, &win_.shown());
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("config")) {
|
||||||
|
nf7::gui::Config(mem_);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Plot::UpdatePlot() noexcept {
|
||||||
|
if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) {
|
||||||
|
ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit);
|
||||||
|
ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit);
|
||||||
|
for (const auto& s : mem_->series) {
|
||||||
|
s.Update();
|
||||||
|
}
|
||||||
|
ImPlot::EndPlot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Plot::Series::Update() const noexcept {
|
||||||
|
switch (type) {
|
||||||
|
case kLine: {
|
||||||
|
const auto Line = [&]<typename T>() {
|
||||||
|
if (data->xs && data->ys) {
|
||||||
|
ImPlot::PlotLine(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
reinterpret_cast<const T*>(data->ys->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
} else if (data->xs) {
|
||||||
|
ImPlot::PlotLine(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->param[0],
|
||||||
|
data->param[1],
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
switch (data->fmt) {
|
||||||
|
case kU8: Line.operator()<uint8_t>(); break;
|
||||||
|
case kS8: Line.operator()<int8_t>(); break;
|
||||||
|
case kU16: Line.operator()<uint16_t>(); break;
|
||||||
|
case kS16: Line.operator()<int16_t>(); break;
|
||||||
|
case kU32: Line.operator()<uint32_t>(); break;
|
||||||
|
case kS32: Line.operator()<int32_t>(); break;
|
||||||
|
case kF32: Line.operator()<float>(); break;
|
||||||
|
case kF64: Line.operator()<double>(); break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case kScatter: {
|
||||||
|
const auto Scatter = [&]<typename T>() {
|
||||||
|
if (data->xs && data->ys) {
|
||||||
|
ImPlot::PlotScatter(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
reinterpret_cast<const T*>(data->ys->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
} else if (data->xs) {
|
||||||
|
ImPlot::PlotScatter(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->param[0],
|
||||||
|
data->param[1],
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
switch (data->fmt) {
|
||||||
|
case kU8: Scatter.operator()<uint8_t>(); break;
|
||||||
|
case kS8: Scatter.operator()<int8_t>(); break;
|
||||||
|
case kU16: Scatter.operator()<uint16_t>(); break;
|
||||||
|
case kS16: Scatter.operator()<int16_t>(); break;
|
||||||
|
case kU32: Scatter.operator()<uint32_t>(); break;
|
||||||
|
case kS32: Scatter.operator()<int32_t>(); break;
|
||||||
|
case kF32: Scatter.operator()<float>(); break;
|
||||||
|
case kF64: Scatter.operator()<double>(); break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case kBars: {
|
||||||
|
const auto Bars = [&]<typename T>() {
|
||||||
|
if (data->xs && data->ys) {
|
||||||
|
ImPlot::PlotBars(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
reinterpret_cast<const T*>(data->ys->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->param[0],
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
} else if (data->xs) {
|
||||||
|
ImPlot::PlotBars(
|
||||||
|
name.c_str(),
|
||||||
|
reinterpret_cast<const T*>(data->xs->data()),
|
||||||
|
static_cast<int>(data->count),
|
||||||
|
data->param[0],
|
||||||
|
data->param[1],
|
||||||
|
data->flags,
|
||||||
|
static_cast<int>(data->offset),
|
||||||
|
static_cast<int>(data->stride));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
switch (data->fmt) {
|
||||||
|
case kU8: Bars.operator()<uint8_t>(); break;
|
||||||
|
case kS8: Bars.operator()<int8_t>(); break;
|
||||||
|
case kU16: Bars.operator()<uint16_t>(); break;
|
||||||
|
case kS16: Bars.operator()<int16_t>(); break;
|
||||||
|
case kU32: Bars.operator()<uint32_t>(); break;
|
||||||
|
case kS32: Bars.operator()<int32_t>(); break;
|
||||||
|
case kF32: Bars.operator()<float>(); break;
|
||||||
|
case kF64: Bars.operator()<double>(); break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string Plot::Data::Stringify() const noexcept {
|
||||||
|
YAML::Emitter st;
|
||||||
|
st << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "series";
|
||||||
|
st << YAML::Value << YAML::BeginMap;
|
||||||
|
for (auto& s : series) {
|
||||||
|
st << YAML::Key << s.name;
|
||||||
|
st << YAML::Value << YAML::BeginMap;
|
||||||
|
st << YAML::Key << "type";
|
||||||
|
st << YAML::Value << std::string {magic_enum::enum_name(s.type)};
|
||||||
|
st << YAML::Key << "fmt" ;
|
||||||
|
st << YAML::Value << std::string {magic_enum::enum_name(s.fmt)};
|
||||||
|
st << YAML::EndMap;
|
||||||
|
}
|
||||||
|
st << YAML::EndMap;
|
||||||
|
st << YAML::EndMap;
|
||||||
|
return std::string {st.c_str(), st.size()};
|
||||||
|
}
|
||||||
|
void Plot::Data::Parse(const std::string& str)
|
||||||
|
try {
|
||||||
|
const auto& yaml = YAML::Load(str);
|
||||||
|
|
||||||
|
std::vector<Series> new_series;
|
||||||
|
for (auto& s : yaml["series"]) {
|
||||||
|
new_series.emplace_back(
|
||||||
|
s.first.as<std::string>(),
|
||||||
|
magic_enum::enum_cast<SeriesType>(s.second["type"].as<std::string>()).value(),
|
||||||
|
magic_enum::enum_cast<SeriesFormat>(s.second["fmt"].as<std::string>()).value());
|
||||||
|
}
|
||||||
|
series = std::move(new_series);
|
||||||
|
} catch (std::bad_optional_access&) {
|
||||||
|
throw nf7::Exception {"unknown enum"};
|
||||||
|
} catch (YAML::Exception& e) {
|
||||||
|
throw nf7::Exception {e.what()};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace nf7
|
||||||
|
|
||||||
|
|
||||||
|
namespace magic_enum::customize {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesType>(nf7::Plot::SeriesType v) noexcept {
|
||||||
|
switch (v) {
|
||||||
|
case nf7::Plot::SeriesType::kLine: return "line";
|
||||||
|
case nf7::Plot::SeriesType::kScatter: return "scatter";
|
||||||
|
case nf7::Plot::SeriesType::kBars: return "bars";
|
||||||
|
}
|
||||||
|
return invalid_tag;
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesFormat>(nf7::Plot::SeriesFormat v) noexcept {
|
||||||
|
switch (v) {
|
||||||
|
case nf7::Plot::SeriesFormat::kU8: return "u8";
|
||||||
|
case nf7::Plot::SeriesFormat::kS8: return "s8";
|
||||||
|
case nf7::Plot::SeriesFormat::kU16: return "u16";
|
||||||
|
case nf7::Plot::SeriesFormat::kS16: return "s16";
|
||||||
|
case nf7::Plot::SeriesFormat::kU32: return "u32";
|
||||||
|
case nf7::Plot::SeriesFormat::kS32: return "s32";
|
||||||
|
case nf7::Plot::SeriesFormat::kF32: return "f32";
|
||||||
|
case nf7::Plot::SeriesFormat::kF64: return "f64";
|
||||||
|
}
|
||||||
|
return invalid_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace magic_enum::customize
|
||||||
|
|
||||||
|
|
||||||
|
namespace yas::detail {
|
||||||
|
|
||||||
|
template <size_t F>
|
||||||
|
struct serializer<
|
||||||
|
yas::detail::type_prop::is_enum,
|
||||||
|
yas::detail::ser_case::use_internal_serializer,
|
||||||
|
F, nf7::Plot::SeriesType> :
|
||||||
|
nf7::EnumSerializer<nf7::Plot::SeriesType> {
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t F>
|
||||||
|
struct serializer<
|
||||||
|
yas::detail::type_prop::is_enum,
|
||||||
|
yas::detail::ser_case::use_internal_serializer,
|
||||||
|
F, nf7::Plot::SeriesFormat> :
|
||||||
|
nf7::EnumSerializer<nf7::Plot::SeriesFormat> {
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yas::detail
|
321
main.cc
321
main.cc
@ -28,22 +28,84 @@
|
|||||||
#include "theme.hh"
|
#include "theme.hh"
|
||||||
|
|
||||||
|
|
||||||
using namespace nf7;
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kSubTaskUnit = 64;
|
||||||
|
|
||||||
|
|
||||||
|
std::atomic<bool> alive_ = true;
|
||||||
|
|
||||||
|
enum CycleState {
|
||||||
|
kSyncUpdate, // -> kUpdate
|
||||||
|
kUpdate, // -> kDraw or kSleep
|
||||||
|
kDraw, // -> kSleep
|
||||||
|
kSleep, // -> kSyncUpdate
|
||||||
|
};
|
||||||
|
std::atomic<CycleState> cycle_ = kUpdate;
|
||||||
|
|
||||||
|
using Task = std::pair<std::shared_ptr<nf7::Context>, nf7::Env::Task>;
|
||||||
|
nf7::Queue<Task> mainq_;
|
||||||
|
nf7::Queue<Task> subq_;
|
||||||
|
nf7::TimedWaitQueue<Task> asyncq_;
|
||||||
|
nf7::Queue<std::exception_ptr> panicq_;
|
||||||
|
|
||||||
|
|
||||||
|
void WorkerThread() noexcept {
|
||||||
|
while (alive_) {
|
||||||
|
// wait for the end of GUI update
|
||||||
|
while (cycle_ == kUpdate) cycle_.wait(kUpdate);
|
||||||
|
|
||||||
|
// exec main tasks
|
||||||
|
while (auto task = mainq_.Pop())
|
||||||
|
try {
|
||||||
|
task->second();
|
||||||
|
} catch (nf7::Exception&) {
|
||||||
|
panicq_.Push(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec sub tasks
|
||||||
|
for (;;) {
|
||||||
|
for (size_t i = 0; i < kSubTaskUnit; ++i) {
|
||||||
|
const auto task = subq_.Pop();
|
||||||
|
if (!task) break;
|
||||||
|
try {
|
||||||
|
task->second();
|
||||||
|
} catch (nf7::Exception&) {
|
||||||
|
panicq_.Push(std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CycleState cycle = cycle_;
|
||||||
|
if (cycle == kSyncUpdate) break;
|
||||||
|
cycle_.wait(cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tell the main thread to start GUI update
|
||||||
|
cycle_ = kUpdate;
|
||||||
|
cycle_.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncThread() noexcept {
|
||||||
|
while (alive_) {
|
||||||
|
asyncq_.Wait(100ms);
|
||||||
|
while (auto task = asyncq_.Pop(nf7::Env::Clock::now()))
|
||||||
|
try {
|
||||||
|
task->second();
|
||||||
|
} catch (nf7::Exception& e) {
|
||||||
|
panicq_.Push(std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Env final : public nf7::Env {
|
class Env final : public nf7::Env {
|
||||||
public:
|
public:
|
||||||
static constexpr auto kFileName = "root.nf7";
|
static constexpr auto kFileName = "root.nf7";
|
||||||
static constexpr size_t kSubTaskUnit = 64;
|
|
||||||
|
|
||||||
Env() noexcept : nf7::Env(std::filesystem::current_path()) {
|
Env() noexcept : nf7::Env(std::filesystem::current_path()) {
|
||||||
// start threads
|
|
||||||
main_thread_ = std::thread([this]() { MainThread(); });
|
|
||||||
async_threads_.resize(std::max<size_t>(std::thread::hardware_concurrency(), 2));
|
|
||||||
for (auto& th : async_threads_) {
|
|
||||||
th = std::thread([this]() { AsyncThread(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// deserialize
|
// deserialize
|
||||||
if (!std::filesystem::exists(kFileName)) {
|
if (!std::filesystem::exists(kFileName)) {
|
||||||
root_ = CreateRoot(*this);
|
root_ = CreateRoot(*this);
|
||||||
@ -53,38 +115,34 @@ class Env final : public nf7::Env {
|
|||||||
nf7::Deserializer::Load(*this, kFileName, root_);
|
nf7::Deserializer::Load(*this, kFileName, root_);
|
||||||
root_->MakeAsRoot();
|
root_->MakeAsRoot();
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::Exception&) {
|
||||||
Panic();
|
panicq_.Push(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~Env() noexcept {
|
|
||||||
if (root_) root_->Isolate();
|
void TearDownRoot() noexcept {
|
||||||
|
if (root_) {
|
||||||
|
Save();
|
||||||
|
root_->Isolate();
|
||||||
root_ = nullptr;
|
root_ = nullptr;
|
||||||
|
}
|
||||||
while (main_.size() || sub_.size() || async_.size()) {
|
|
||||||
std::this_thread::sleep_for(100ms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alive_ = false;
|
void ExecMain(const std::shared_ptr<nf7::Context>& ctx,
|
||||||
|
Task&& task) noexcept override {
|
||||||
cv_.notify_one();
|
mainq_.Push({ctx, std::move(task)});
|
||||||
main_thread_.join();
|
}
|
||||||
|
void ExecSub(const std::shared_ptr<nf7::Context>& ctx,
|
||||||
async_.Notify();
|
Task&& task) noexcept override {
|
||||||
for (auto& th : async_threads_) th.join();
|
subq_.Push({ctx, std::move(task)});
|
||||||
|
cycle_.notify_all();
|
||||||
|
}
|
||||||
|
void ExecAsync(const std::shared_ptr<nf7::Context>& ctx,
|
||||||
|
Task&& task, Time time) noexcept override {
|
||||||
|
asyncq_.Push(time, {ctx, std::move(task)});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExecMain(const std::shared_ptr<Context>& ctx, Task&& task) noexcept override {
|
void Handle(const nf7::File::Event& e) noexcept override
|
||||||
main_.Push({ctx, std::move(task)});
|
|
||||||
}
|
|
||||||
void ExecSub(const std::shared_ptr<Context>& ctx, Task&& task) noexcept override {
|
|
||||||
sub_.Push({ctx, std::move(task)});
|
|
||||||
}
|
|
||||||
void ExecAsync(const std::shared_ptr<Context>& ctx, Task&& task, Time time) noexcept override {
|
|
||||||
async_.Push(time, {ctx, std::move(task)});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Handle(const File::Event& e) noexcept override
|
|
||||||
try {
|
try {
|
||||||
// trigger File::Handle()
|
// trigger File::Handle()
|
||||||
GetFileOrThrow(e.id).Handle(e);
|
GetFileOrThrow(e.id).Handle(e);
|
||||||
@ -97,27 +155,23 @@ class Env final : public nf7::Env {
|
|||||||
|
|
||||||
// trigger global watcher
|
// trigger global watcher
|
||||||
for (auto w : watchers_map_[0]) w->Handle(e);
|
for (auto w : watchers_map_[0]) w->Handle(e);
|
||||||
} catch (ExpiredException&) {
|
} catch (nf7::ExpiredException&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exit() noexcept override {
|
void Exit() noexcept override {
|
||||||
exit_requested_ = true;
|
exit_requested_ = true;
|
||||||
}
|
}
|
||||||
void Save() noexcept override {
|
void Save() noexcept override
|
||||||
try {
|
try {
|
||||||
nf7::Serializer::Save(kFileName, root_);
|
nf7::Serializer::Save(*this, kFileName, root_);
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::Exception&) {
|
||||||
Panic();
|
panicq_.Push(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
void Throw(std::exception_ptr&& ptr) noexcept override {
|
||||||
void Throw(std::exception_ptr&& eptr) noexcept override {
|
panicq_.Push(std::move(ptr));
|
||||||
Panic(std::move(eptr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() noexcept {
|
void Update() noexcept {
|
||||||
interrupt_ = true;
|
|
||||||
std::unique_lock<std::mutex> _(mtx_);
|
|
||||||
|
|
||||||
ImGui::PushID(this);
|
ImGui::PushID(this);
|
||||||
{
|
{
|
||||||
if (root_) {
|
if (root_) {
|
||||||
@ -125,35 +179,31 @@ class Env final : public nf7::Env {
|
|||||||
root_->Update();
|
root_->Update();
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
UpdatePanic();
|
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
|
||||||
interrupt_ = false;
|
|
||||||
cv_.notify_one();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool exitRequested() const noexcept { return exit_requested_; }
|
bool exitRequested() const noexcept { return exit_requested_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
File* GetFile(File::Id id) const noexcept override {
|
nf7::File* GetFile(nf7::File::Id id) const noexcept override {
|
||||||
auto itr = files_.find(id);
|
auto itr = files_.find(id);
|
||||||
return itr != files_.end()? itr->second: nullptr;
|
return itr != files_.end()? itr->second: nullptr;
|
||||||
}
|
}
|
||||||
File::Id AddFile(File& f) noexcept override {
|
nf7::File::Id AddFile(nf7::File& f) noexcept override {
|
||||||
auto [itr, ok] = files_.emplace(file_next_++, &f);
|
auto [itr, ok] = files_.emplace(file_next_++, &f);
|
||||||
assert(ok);
|
assert(ok);
|
||||||
return itr->first;
|
return itr->first;
|
||||||
}
|
}
|
||||||
void RemoveFile(File::Id id) noexcept override {
|
void RemoveFile(nf7::File::Id id) noexcept override {
|
||||||
files_.erase(id);
|
files_.erase(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddWatcher(File::Id id, Watcher& w) noexcept override {
|
void AddWatcher(nf7::File::Id id, nf7::Env::Watcher& w) noexcept override {
|
||||||
watchers_map_[id].push_back(&w);
|
watchers_map_[id].push_back(&w);
|
||||||
watchers_rmap_[&w].push_back(id);
|
watchers_rmap_[&w].push_back(id);
|
||||||
}
|
}
|
||||||
void RemoveWatcher(Watcher& w) noexcept override {
|
void RemoveWatcher(nf7::Env::Watcher& w) noexcept override {
|
||||||
for (const auto id : watchers_rmap_[&w]) {
|
for (const auto id : watchers_rmap_[&w]) {
|
||||||
auto& v = watchers_map_[id];
|
auto& v = watchers_map_[id];
|
||||||
v.erase(std::remove(v.begin(), v.end(), &w), v.end());
|
v.erase(std::remove(v.begin(), v.end(), &w), v.end());
|
||||||
@ -162,36 +212,27 @@ class Env final : public nf7::Env {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<File> root_;
|
|
||||||
|
|
||||||
std::atomic<bool> exit_requested_ = false;
|
std::atomic<bool> exit_requested_ = false;
|
||||||
std::atomic<bool> alive_ = true;
|
|
||||||
std::exception_ptr panic_;
|
|
||||||
|
|
||||||
File::Id file_next_ = 1;
|
std::unique_ptr<nf7::File> root_;
|
||||||
std::unordered_map<File::Id, File*> files_;
|
|
||||||
|
|
||||||
std::unordered_map<File::Id, std::vector<Watcher*>> watchers_map_;
|
nf7::File::Id file_next_ = 1;
|
||||||
std::unordered_map<Watcher*, std::vector<File::Id>> watchers_rmap_;
|
std::unordered_map<nf7::File::Id, nf7::File*> files_;
|
||||||
|
|
||||||
using TaskItem = std::pair<std::shared_ptr<nf7::Context>, Task>;
|
std::unordered_map<nf7::File::Id, std::vector<nf7::Env::Watcher*>> watchers_map_;
|
||||||
nf7::Queue<TaskItem> main_;
|
std::unordered_map<nf7::Env::Watcher*, std::vector<nf7::File::Id>> watchers_rmap_;
|
||||||
nf7::Queue<TaskItem> sub_;
|
};
|
||||||
nf7::TimedWaitQueue<TaskItem> async_;
|
|
||||||
|
|
||||||
std::mutex mtx_;
|
|
||||||
std::condition_variable cv_;
|
|
||||||
std::atomic<bool> interrupt_ = false;
|
|
||||||
std::thread main_thread_;
|
|
||||||
std::vector<std::thread> async_threads_;
|
|
||||||
|
|
||||||
|
|
||||||
void Panic(std::exception_ptr ptr = std::current_exception()) noexcept {
|
void UpdatePanic() noexcept {
|
||||||
panic_ = ptr;
|
static std::exception_ptr ptr_;
|
||||||
|
if (!ptr_) {
|
||||||
|
if (auto ptr = panicq_.Pop()) {
|
||||||
|
ptr_ = *ptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void UpdatePanic() noexcept {
|
|
||||||
const auto em = ImGui::GetFontSize();
|
|
||||||
|
|
||||||
|
const auto em = ImGui::GetFontSize();
|
||||||
ImGui::SetNextWindowSize({32*em, 24*em}, ImGuiCond_Appearing);
|
ImGui::SetNextWindowSize({32*em, 24*em}, ImGuiCond_Appearing);
|
||||||
if (ImGui::BeginPopupModal("panic")) {
|
if (ImGui::BeginPopupModal("panic")) {
|
||||||
ImGui::TextUnformatted("something went wrong X(");
|
ImGui::TextUnformatted("something went wrong X(");
|
||||||
@ -201,11 +242,11 @@ class Env final : public nf7::Env {
|
|||||||
|
|
||||||
const auto kFlags = ImGuiWindowFlags_HorizontalScrollbar;
|
const auto kFlags = ImGuiWindowFlags_HorizontalScrollbar;
|
||||||
if (ImGui::BeginChild("panic_detail", size, true, kFlags)) {
|
if (ImGui::BeginChild("panic_detail", size, true, kFlags)) {
|
||||||
auto ptr = panic_;
|
auto ptr = ptr_;
|
||||||
while (ptr)
|
while (ptr)
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(ptr);
|
std::rethrow_exception(ptr);
|
||||||
} catch (Exception& e) {
|
} catch (nf7::Exception& e) {
|
||||||
e.UpdatePanic();
|
e.UpdatePanic();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ptr = e.reason();
|
ptr = e.reason();
|
||||||
@ -223,88 +264,50 @@ class Env final : public nf7::Env {
|
|||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("ignore")) {
|
if (ImGui::Button("ignore")) {
|
||||||
panic_ = {};
|
ptr_ = {};
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
if (const auto rem = panicq_.size()) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("other %zu exceptions are also causing panic", rem);
|
||||||
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
} else {
|
} else {
|
||||||
if (panic_) ImGui::OpenPopup("panic");
|
if (ptr_) ImGui::OpenPopup("panic");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void MainThread() noexcept {
|
|
||||||
std::unique_lock<std::mutex> k(mtx_);
|
|
||||||
while (alive_) {
|
|
||||||
// exec main tasks
|
|
||||||
while (auto task = main_.Pop())
|
|
||||||
try {
|
|
||||||
task->second();
|
|
||||||
} 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->second();
|
|
||||||
} catch (Exception&) {
|
|
||||||
Panic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!alive_) return;
|
|
||||||
}
|
|
||||||
cv_.wait(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void AsyncThread() noexcept {
|
|
||||||
while (alive_) {
|
|
||||||
const auto now = Clock::now();
|
|
||||||
while (auto task = async_.Pop(now))
|
|
||||||
try {
|
|
||||||
task->second();
|
|
||||||
} catch (Exception& e) {
|
|
||||||
std::cout << "async thread exception: " << e.msg() << std::endl;
|
|
||||||
}
|
|
||||||
if (!alive_) break;
|
|
||||||
async_.Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int, char**) {
|
int main(int, char**) {
|
||||||
// init display
|
// init GLFW
|
||||||
glfwSetErrorCallback(
|
glfwSetErrorCallback(
|
||||||
[](int, const char* msg) {
|
[](int, const char* msg) {
|
||||||
std::cout << "GLFW error: " << msg << std::endl;
|
std::cout << "GLFW error: " << msg << std::endl;
|
||||||
});
|
});
|
||||||
if (!glfwInit()) return 1;
|
if (!glfwInit()) return 1;
|
||||||
|
|
||||||
|
// create window
|
||||||
GLFWwindow* window;
|
GLFWwindow* window;
|
||||||
const char* glsl_version;
|
|
||||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||||
# if defined(__APPLE__)
|
|
||||||
glsl_version = "#version 150";
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|
||||||
# else
|
|
||||||
glsl_version = "#version 130";
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
# endif
|
|
||||||
window = glfwCreateWindow(1280, 720, "Nf7", NULL, NULL);
|
window = glfwCreateWindow(1280, 720, "Nf7", NULL, NULL);
|
||||||
if (window == NULL) return 1;
|
if (window == NULL) return 1;
|
||||||
glfwMakeContextCurrent(window);
|
glfwMakeContextCurrent(window);
|
||||||
glfwSwapInterval(1);
|
glfwSwapInterval(1);
|
||||||
if (glewInit() != GLEW_OK) return 1;
|
if (glewInit() != GLEW_OK) return 1;
|
||||||
|
|
||||||
|
// start threads
|
||||||
|
auto th_worker = std::thread {WorkerThread};
|
||||||
|
|
||||||
|
const auto cores = std::thread::hardware_concurrency();
|
||||||
|
std::vector<std::thread> th_async(cores > 3? cores-2: 1);
|
||||||
|
for (auto& th : th_async) th = std::thread {AsyncThread};
|
||||||
|
|
||||||
// init ImGUI
|
// init ImGUI
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
@ -316,40 +319,76 @@ int main(int, char**) {
|
|||||||
|
|
||||||
SetUpImGuiStyle();
|
SetUpImGuiStyle();
|
||||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
ImGui_ImplOpenGL3_Init("#version 130");
|
||||||
|
|
||||||
// main logic
|
// main loop
|
||||||
{
|
|
||||||
::Env env;
|
::Env env;
|
||||||
glfwShowWindow(window);
|
glfwShowWindow(window);
|
||||||
while (!glfwWindowShouldClose(window) && !env.exitRequested()) {
|
while (!glfwWindowShouldClose(window) && !env.exitRequested()) {
|
||||||
// new frame
|
// handle events
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
ImGui_ImplOpenGL3_NewFrame();
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
env.Update();
|
// sync with worker thread
|
||||||
|
cycle_ = kSyncUpdate;
|
||||||
|
cycle_.notify_all();
|
||||||
|
while (cycle_ == kSyncUpdate) cycle_.wait(kSyncUpdate);
|
||||||
|
|
||||||
// render windows
|
// GUI update (OpenGL call is forbidden)
|
||||||
|
assert(cycle_ == kUpdate);
|
||||||
|
env.Update();
|
||||||
|
UpdatePanic();
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
||||||
|
// GUI draw (OpenGL calls occur)
|
||||||
|
cycle_ = kDraw;
|
||||||
|
cycle_.notify_all();
|
||||||
|
glfwMakeContextCurrent(window);
|
||||||
int w, h;
|
int w, h;
|
||||||
glfwGetFramebufferSize(window, &w, &h);
|
glfwGetFramebufferSize(window, &w, &h);
|
||||||
glViewport(0, 0, w, h);
|
glViewport(0, 0, w, h);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
glfwMakeContextCurrent(nullptr);
|
||||||
env.Save();
|
|
||||||
|
cycle_ = kSleep;
|
||||||
|
cycle_.notify_all();
|
||||||
|
// TODO sleep
|
||||||
}
|
}
|
||||||
|
|
||||||
// teardown ImGUI
|
// sync with worker thread and tear down filesystem
|
||||||
|
cycle_ = kSyncUpdate;
|
||||||
|
cycle_.notify_all();
|
||||||
|
while (cycle_ == kSyncUpdate) cycle_.wait(kSyncUpdate);
|
||||||
|
env.TearDownRoot();
|
||||||
|
|
||||||
|
// notify other threads that the destruction is done
|
||||||
|
cycle_ = kSleep;
|
||||||
|
cycle_.notify_all();
|
||||||
|
|
||||||
|
// wait for all tasks
|
||||||
|
while (mainq_.size() || subq_.size() || asyncq_.size()) {
|
||||||
|
std::this_thread::sleep_for(30ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit all threads
|
||||||
|
alive_ = false;
|
||||||
|
cycle_ = kSyncUpdate;
|
||||||
|
cycle_.notify_all();
|
||||||
|
asyncq_.Notify();
|
||||||
|
for (auto& th : th_async) th.join();
|
||||||
|
th_worker.join();
|
||||||
|
|
||||||
|
// tear down ImGUI
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImPlot::DestroyContext();
|
ImPlot::DestroyContext();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
// teardown display
|
// tear down display
|
||||||
glfwDestroyWindow(window);
|
glfwDestroyWindow(window);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 0;
|
return 0;
|
||||||
|
4
nf7.cc
4
nf7.cc
@ -293,7 +293,7 @@ Serializer::ChunkGuard::~ChunkGuard() noexcept {
|
|||||||
*ar_ & static_cast<uint64_t>(end - begin_);
|
*ar_ & static_cast<uint64_t>(end - begin_);
|
||||||
ar_->st_->Seek(end);
|
ar_->st_->Seek(end);
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::Exception&) {
|
||||||
// TODO
|
ar_->env_->Throw(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ Deserializer::ChunkGuard::~ChunkGuard() {
|
|||||||
ar_->st_->Seek(end);
|
ar_->st_->Seek(end);
|
||||||
}
|
}
|
||||||
} catch (nf7::Exception&) {
|
} catch (nf7::Exception&) {
|
||||||
// TODO
|
ar_->env_->Throw(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Deserializer::ChunkGuard::ValidateEnd() {
|
void Deserializer::ChunkGuard::ValidateEnd() {
|
||||||
|
9
nf7.hh
9
nf7.hh
@ -383,14 +383,14 @@ class Serializer final :
|
|||||||
size_t begin_;
|
size_t begin_;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void Save(const char* path, auto& v) {
|
static void Save(nf7::Env& env, const char* path, auto& v) {
|
||||||
SerializerStream st {path, "wb"};
|
SerializerStream st {path, "wb"};
|
||||||
Serializer ar {st};
|
Serializer ar {env, st};
|
||||||
ar(v);
|
ar(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serializer(nf7::SerializerStream& st) :
|
Serializer(nf7::Env& env, nf7::SerializerStream& st) :
|
||||||
binary_ostream(st), oarchive_header(st), st_(&st) {
|
binary_ostream(st), oarchive_header(st), env_(&env), st_(&st) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -413,6 +413,7 @@ class Serializer final :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
nf7::Env* const env_;
|
||||||
nf7::SerializerStream* const st_;
|
nf7::SerializerStream* const st_;
|
||||||
};
|
};
|
||||||
class Deserializer final :
|
class Deserializer final :
|
||||||
|
21
thirdparty/CMakeLists.txt
vendored
21
thirdparty/CMakeLists.txt
vendored
@ -117,7 +117,7 @@ target_sources(imnodes
|
|||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
implot
|
implot
|
||||||
URL "https://github.com/epezent/implot/archive/refs/heads/master.zip"
|
URL "https://github.com/epezent/implot/archive/refs/tags/v0.14.zip"
|
||||||
)
|
)
|
||||||
FetchContent_Populate(implot)
|
FetchContent_Populate(implot)
|
||||||
|
|
||||||
@ -177,6 +177,14 @@ endfunction()
|
|||||||
include_luajit()
|
include_luajit()
|
||||||
|
|
||||||
|
|
||||||
|
# ---- magic_enum ----
|
||||||
|
FetchContent_Declare(
|
||||||
|
magic_enum
|
||||||
|
URL "https://github.com/Neargye/magic_enum/archive/refs/tags/v0.8.1.zip"
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(magic_enum)
|
||||||
|
|
||||||
|
|
||||||
# ---- miniaudio ----
|
# ---- miniaudio ----
|
||||||
# repository: https://github.com/mackron/miniaudio
|
# repository: https://github.com/mackron/miniaudio
|
||||||
# license : Unlicense
|
# license : Unlicense
|
||||||
@ -203,6 +211,17 @@ target_include_directories(source_location SYSTEM INTERFACE .)
|
|||||||
target_sources(source_location INTERFACE source_location.hh)
|
target_sources(source_location INTERFACE source_location.hh)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- yaml-cpp ----
|
||||||
|
# repository: https://github.com/jbeder/yaml-cpp
|
||||||
|
# license : MIT
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
yaml-cpp
|
||||||
|
URL "https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip"
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(yaml-cpp)
|
||||||
|
|
||||||
|
|
||||||
# ---- yas ----
|
# ---- yas ----
|
||||||
# repository: https://github.com/niXman/yas
|
# repository: https://github.com/niXman/yas
|
||||||
# license : Boost
|
# license : Boost
|
||||||
|
Loading…
x
Reference in New Issue
Block a user