Compare commits

...

33 Commits

Author SHA1 Message Date
615d2eacb0 fix an issue that tasks pushed to Audio/Context aren't run when nf7 is shutting down 2022-09-28 13:46:45 +09:00
c2c4b83918 improve main.cc readability 2022-09-28 13:28:27 +09:00
4f13dd9456 refactor Audio/Device 2022-09-27 11:09:45 +09:00
7b2f9c8d55 improve device list of Audio/Context 2022-09-26 23:32:34 +09:00
b949383932 fix an issue that node sockets can be duplicated 2022-09-26 12:45:05 +09:00
5ef347fa2e fix an issue that global table is not applied in lua's main thread 2022-09-26 12:31:06 +09:00
f77a60831c refactor LuaJIT/InlineNode 2022-09-26 12:18:49 +09:00
8688ef98b6 add nf7::gui::Config 2022-09-26 12:00:46 +09:00
d0d6a2ebd5 refactor LuaJIT/Node 2022-09-26 12:00:46 +09:00
ab802d02e3 improve nf7::Future for using with const qulifier 2022-09-26 12:00:46 +09:00
007882ccfd fix data race 2022-09-26 02:15:13 +09:00
2082a6e482 enhance Future::Then() 2022-09-26 02:15:13 +09:00
8ffad3347f rename NativeFile -> NFile 2022-09-26 02:15:13 +09:00
7275e9a710 add NFileWatcher 2022-09-26 02:15:13 +09:00
46e6a78682 allow GenericMemento to access data through arrow operator
the arrow allows
2022-09-26 02:15:13 +09:00
09375ced9c restore nf7::Task once removed 2022-09-26 02:15:13 +09:00
79f3cc9639 add bitmanip functions to Lua script std table 2022-09-24 13:46:37 +09:00
f16937da5a update Lua std library 2022-09-23 10:33:02 +09:00
7dbda8d281 add Value/Plot 2022-09-23 09:36:16 +09:00
8879e9ed41 update a thirdparty lib, ImPlot 2022-09-23 09:36:16 +09:00
e045e86b11 add nf7::gui::ConfigPopup 2022-09-22 17:22:45 +09:00
07b198f71e generalize nf7::util::Uniq 2022-09-22 17:22:45 +09:00
b004723464 add new thirdparty library, yaml-cpp 2022-09-22 17:22:45 +09:00
6e820daef8 add new thirdparty library, magic_enum
this is awesome X)
2022-09-20 22:27:00 +09:00
dcbd3594cf add EnumSerializer 2022-09-19 10:04:30 +09:00
c5590092fa fix synchronization issues 2022-09-15 19:36:21 +09:00
8339cc814a enable sanitizers on g++ 2022-09-15 19:36:00 +09:00
bbfee304bd add 'perform GC cycle' option to LuaJIT/Context menu 2022-09-14 15:38:08 +09:00
2040898bd7 fix an issue that items are not kept sorted in layers of Sequencer/Timeline 2022-09-14 15:25:47 +09:00
46ddb16128 implement exception handling in some complicated cases 2022-09-14 15:25:14 +09:00
d6a9c62a63 fix an issue that NodeRootSelectLambda violates assetion 2022-09-02 17:42:33 +09:00
a5f3e459bf enhance locking feature of System/NativeFile 2022-09-02 17:42:33 +09:00
336f436942 add Mutex 2022-09-02 17:42:33 +09:00
39 changed files with 2520 additions and 1688 deletions

View File

@ -5,13 +5,9 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
project(nf7 C CXX)
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")
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(NF7_CXX_FLAGS
set(NF7_OPTIONS_WARNING
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
@ -19,6 +15,23 @@ set(NF7_CXX_FLAGS
$<$<CXX_COMPILER_ID:MSVC>:
/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)
@ -27,9 +40,14 @@ add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
# ---- application ----
add_executable(nf7)
target_compile_options(nf7 PRIVATE ${NF7_CXX_FLAGS})
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
PRIVATE
IMGUI_DEFINE_MATH_OPERATORS
@ -59,6 +77,7 @@ target_sources(nf7
common/generic_type_info.hh
common/generic_watcher.hh
common/gui_dnd.hh
common/gui_config.hh
common/gui_context.hh
common/gui_file.hh
common/gui_file.cc
@ -83,20 +102,25 @@ target_sources(nf7
common/memento.hh
common/memento_recorder.hh
common/mutable_memento.hh
common/native_file.hh
common/mutex.hh
common/nfile.hh
common/nfile_watcher.hh
common/node.hh
common/node_link_store.hh
common/node_root_lambda.hh
common/node_root_select_lambda.hh
common/ptr_selector.hh
common/queue.hh
common/ring_buffer.hh
common/sequencer.hh
common/squashed_history.hh
common/task.hh
common/thread.hh
common/timed_queue.hh
common/util_algorithm.hh
common/util_string.hh
common/value.hh
common/yas_audio.hh
common/yas_enum.hh
common/yas_imgui.hh
common/yas_imnodes.hh
common/yas_nf7.hh
@ -104,8 +128,8 @@ target_sources(nf7
common/yas_std_filesystem.hh
common/yas_std_variant.hh
$<$<PLATFORM_ID:Linux>:common/native_file_unix.cc>
$<$<PLATFORM_ID:Windows>:common/native_file_win.cc>
$<$<PLATFORM_ID:Linux>:common/nfile_unix.cc>
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
file/audio_context.cc
file/audio_device.cc
@ -123,8 +147,9 @@ target_sources(nf7
file/system_event.cc
file/system_imgui.cc
file/system_logger.cc
file/system_native_file.cc
file/system_nfile.cc
file/value_curve.cc
file/value_plot.cc
)
target_link_libraries(nf7
PRIVATE
@ -135,7 +160,9 @@ target_link_libraries(nf7
implot
linalg.h
luajit
magic_enum
miniaudio
source_location
yas
yaml-cpp
)

View File

@ -24,16 +24,22 @@ namespace nf7 {
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
//
// 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>
class Future final {
public:
class Promise;
class Coro;
using ThisFuture = nf7::Future<T>;
using Handle = std::coroutine_handle<Promise>;
using Imm = std::variant<T, std::exception_ptr>;
@ -59,7 +65,7 @@ class Future final {
// Factory side have this to tell finish or abort.
class Promise final {
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>::Coro;
@ -90,11 +96,14 @@ class Future final {
auto Return(T&& v) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->state = kDone;
data_->value = std::move(v);
data_->state = kDone;
CallReceivers();
}
}
auto Return(const T& v) noexcept {
Return(T {v});
}
// thread-safe
void Throw(std::exception_ptr e) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
@ -104,21 +113,25 @@ class Future final {
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
// Do Return(f()) if no exception is thrown, otherwise call Throw().
auto Wrap(const std::function<T()>& f) noexcept
try {
Return(f());
} catch (Exception&) {
} catch (...) {
Throw(std::current_exception());
}
// thread-safe
// Creates Future() object.
Future future() const noexcept {
ThisFuture future() const noexcept {
assert(data_);
return Future(data_);
return ThisFuture(data_);
}
auto get_return_object() noexcept {
@ -179,14 +192,14 @@ class Future final {
Coro& operator=(const Coro&) = delete;
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(); });
data_->ctx = ctx;
return Future(data_);
return ThisFuture(data_);
}
void Abort() noexcept {
h_.promise().Throw(
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
data_->aborted = true;
}
@ -208,43 +221,75 @@ class Future final {
}
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
Future(const ThisFuture&) = default;
Future(ThisFuture&&) = default;
Future& operator=(const ThisFuture&) = default;
Future& operator=(ThisFuture&&) = default;
// Schedules to execute f() immediately on any thread
// 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_) {
std::unique_lock<std::mutex> k(data_->mtx);
if (yet()) {
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;
}
}
f(*this);
fun(*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.
Future& ThenSub(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(Future)>&& f) noexcept {
if (data_) {
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)));
// same as Then() but called when it's done without error
ThisFuture& ThenIf(const std::shared_ptr<nf7::Context>& ctx, std::function<void(const T&)>&& f) noexcept {
Then(ctx, [f = std::move(f)](auto& fu) {
if (fu.done()) f(fu.value());
});
return *this;
}
}
ctx->env().ExecSub(ctx, std::bind(f, *this));
return *this;
ThisFuture& ThenIf(auto&& f) noexcept {
return ThenIf(nullptr, std::move(f));
}
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 (std::holds_alternative<T>(*imm_)) return std::get<T>(*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);
auto callee_ctx = data.ctx.lock();
assert(callee_ctx);
auto caller_data = caller.promise().data__();
auto caller_ctx = caller_data->ctx.lock();
@ -306,7 +350,7 @@ class Future final {
caller.resume();
}
}
auto await_resume() { return value(); }
auto& await_resume() { return value(); }
private:
std::optional<Imm> imm_;

View File

@ -29,6 +29,13 @@ class GenericMemento : public nf7::MutableMemento {
assert(map_.empty());
}
T* operator->() noexcept {
return &data_;
}
const T* operator->() const noexcept {
return &data_;
}
std::shared_ptr<Tag> Save() noexcept override {
if (tag_) return tag_;
auto [itr, emplaced] = map_.emplace(next_++, data_);

59
common/gui_config.hh Normal file
View 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

View File

@ -4,6 +4,8 @@
#include "nf7.hh"
#include "common/util_algorithm.hh"
namespace nf7::gui {

View File

@ -29,6 +29,8 @@ class Popup {
return ImGui::BeginPopup(name_, flags_);
}
const char* name() const noexcept { return name_; }
private:
const char* name_;
ImGuiWindowFlags flags_;
@ -36,6 +38,7 @@ class Popup {
std::optional<ImGuiPopupFlags> open_flags_;
};
class IOSocketListPopup final :
public nf7::FileBase::Feature, private Popup {
public:

View File

@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <cassert>
#include <memory>
@ -8,11 +9,6 @@
namespace nf7 {
class LifeExpiredException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
template <typename T>
class Life final {
public:
@ -33,7 +29,7 @@ class Life final {
T* const ptr_;
struct Data final {
T* ptr;
std::atomic<T*> ptr;
};
std::shared_ptr<Data> data_;
};
@ -57,7 +53,7 @@ class Life<T>::Ref final {
void EnforceAlive() const {
if (!data_->ptr) {
throw LifeExpiredException {"target expired"};
throw nf7::ExpiredException {"target expired"};
}
}

View File

@ -17,11 +17,7 @@
namespace nf7::luajit {
// pushes original libraries
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;
static void PushStd(lua_State* L) noexcept;
// buffer <-> lua value conversion
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 {
if (luaL_newmetatable(L, "nf7::luajit::PushGlobalTable")) {
PushLuaLib(L);
lua_setfield(L, -2, "lua");
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");
PushStd(L);
lua_setfield(L, -2, "std");
}
}
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_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// ---- lua lib ----
// assert(expr[, msg])
lua_pushcfunction(L, [](auto L) {
if (lua_toboolean(L, 1)) {
return 0;
@ -457,11 +401,13 @@ static void PushLuaLib(lua_State* L) noexcept {
});
lua_setfield(L, -2, "assert");
// error(msg)
lua_pushcfunction(L, [](auto L) {
return luaL_error(L, luaL_checkstring(L, 1));
});
lua_setfield(L, -2, "error");
// load(str)
lua_pushcfunction(L, [](auto L) {
if (0 != luaL_loadstring(L, luaL_checkstring(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");
// pcall(func, args...) -> success, result
lua_pushcfunction(L, [](auto L) {
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
lua_pushboolean(L, true);
@ -482,44 +429,35 @@ static void PushLuaLib(lua_State* L) noexcept {
}
});
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_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "sin");
// cos(theta)
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "cos");
// tan(slope)
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
return 1;
});
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.setmetatable(table, meta_table)
// ---- table lib ----
// meta(table, meta_table)
lua_pushcfunction(L, [](auto L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TTABLE);
@ -527,18 +465,12 @@ static void PushTableLib(lua_State* L) noexcept {
lua_setmetatable(L, 1);
return 1;
});
lua_setfield(L, -2, "setmetatable");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
static void PushTimeLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_setfield(L, -2, "meta");
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// time.now()
// ---- time lib ----
// now()
lua_pushcfunction(L, [](auto L) {
const auto now = nf7::Env::Clock::now().time_since_epoch();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
@ -546,6 +478,51 @@ static void PushTimeLib(lua_State* L) noexcept {
return 1;
});
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_setmetatable(L, -2);

View File

@ -124,4 +124,17 @@ inline nf7::Value CheckValue(lua_State* L, int idx) {
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

View File

@ -23,8 +23,6 @@ lua_State* Thread::Init(lua_State* L) noexcept {
assert(state_ == kInitial);
th_ = lua_newthread(L);
PushImmEnv(L);
lua_setfenv(L, -2);
th_ref_.emplace(ctx_, ljq_, L);
state_ = kPaused;
@ -36,24 +34,24 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
if (state_ == kAborted) return;
assert(L == th_);
assert(state_ == kPaused);
(void) L;
static const auto kHook = [](auto L, auto) {
luaL_error(L, "reached instruction limit (<=1e7)");
};
lua_sethook(th_, kHook, LUA_MASKCOUNT, kInstructionLimit);
lua_sethook(L, kHook, LUA_MASKCOUNT, kInstructionLimit);
PushGlobalTable(th_);
PushWeakPtr(th_, weak_from_this());
PushMeta(th_);
lua_setmetatable(th_, -2);
lua_setfield(th_, -2, "nf7");
lua_pop(th_, 1);
// set global table
PushGlobalTable(L);
PushWeakPtr(L, weak_from_this());
PushMeta(L);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "nf7");
lua_pop(L, 1);
state_ = kRunning;
k.unlock();
active_ = true;
const auto ret = lua_resume(th_, narg);
const auto ret = lua_resume(L, narg);
active_ = false;
k.lock();
if (state_ == kAborted) return;
@ -68,7 +66,7 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
state_ = kAborted;
}
if (!std::exchange(skip_handle_, false)) {
handler_(*this, th_);
handler_(*this, L);
}
}
void Thread::Abort() noexcept {
@ -237,13 +235,10 @@ static void PushMeta(lua_State* L) noexcept {
return 0;
}
} else {
fu.Then([L, th](auto fu) {
try {
const auto& p = fu.value();
fu.ThenIf([L, th](auto& p) {
th->ExecResume(L, p.first, p.second);
} catch (nf7::Exception& e) {
}).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) {
th->ExecResume(L);
}
});
th->ExpectYield(L);
return lua_yield(L, 0);

View File

@ -138,16 +138,16 @@ class Thread final : public std::enable_shared_from_this<Thread> {
template <typename T>
Thread::Handler Thread::CreatePromiseHandler(
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()) {
case kPaused:
pro.Throw(std::make_exception_ptr<nf7::Exception>({"unexpected yield"}));
pro.template Throw<nf7::Exception>("unexpected yield");
break;
case kFinished:
pro.Wrap([&]() { return f(L); });
break;
case kAborted:
pro.Throw(std::make_exception_ptr<nf7::Exception>({lua_tostring(L, -1)}));
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
break;
default:
assert(false);

109
common/mutex.hh Normal file
View 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

View File

@ -11,7 +11,7 @@
namespace nf7 {
class NativeFile final : public nf7::Context {
class NFile final {
public:
class Exception final : public nf7::Exception {
using nf7::Exception::Exception;
@ -23,17 +23,16 @@ class NativeFile final : public nf7::Context {
};
using Flags = uint8_t;
NativeFile() = delete;
NativeFile(nf7::Env& env, nf7::File::Id id,
const std::filesystem::path& path, Flags flags) :
Context(env, id), path_(path), flags_(flags) {
NFile() = delete;
NFile(const std::filesystem::path& path, Flags flags) :
path_(path), flags_(flags) {
Init();
}
~NativeFile() noexcept;
NativeFile(const NativeFile&) = delete;
NativeFile(NativeFile&&) = delete;
NativeFile& operator=(const NativeFile&) = delete;
NativeFile& operator=(NativeFile&&) = delete;
~NFile() noexcept;
NFile(const NFile&) = delete;
NFile(NFile&&) = delete;
NFile& operator=(const NFile&) = delete;
NFile& operator=(NFile&&) = delete;
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);

View File

@ -1,4 +1,4 @@
#include "common/native_file.hh"
#include "common/nfile.hh"
extern "C" {
#include <fcntl.h>
@ -11,7 +11,7 @@ extern "C" {
namespace nf7 {
void NativeFile::Init() {
void NFile::Init() {
int flags = 0;
if ((flags_ & kRead) && (flags_ & kWrite)) {
flags |= O_RDWR | O_CREAT;
@ -23,45 +23,45 @@ void NativeFile::Init() {
int fd = open(path_.string().c_str(), flags, 0600);
if (fd < 0) {
throw NativeFile::Exception {"open failure"};
throw NFile::Exception {"open failure"};
}
handle_ = static_cast<uint64_t>(fd);
}
NativeFile::~NativeFile() noexcept {
NFile::~NFile() noexcept {
const auto fd = static_cast<int>(handle_);
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 off = static_cast<off_t>(offset);
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);
if (ret == -1) {
throw NativeFile::Exception {"read failure"};
throw NFile::Exception {"read failure"};
}
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 off = static_cast<off_t>(offset);
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);
if (ret == -1) {
throw nf7::NativeFile::Exception {"write failure"};
throw nf7::NFile::Exception {"write failure"};
}
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_);
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
throw nf7::NativeFile::Exception {"ftruncate failure"};
throw nf7::NFile::Exception {"ftruncate failure"};
}
return size;
}

46
common/nfile_watcher.hh Normal file
View 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

View File

@ -1,4 +1,4 @@
#include "common/native_file.hh"
#include "common/nfile.hh"
extern "C" {
#include <windows.h>
@ -7,7 +7,7 @@ extern "C" {
namespace nf7 {
void NativeFile::Init() {
void NFile::Init() {
DWORD acc = 0;
DWORD flags = 0;
if (flags_ & kRead) {
@ -24,55 +24,55 @@ void NativeFile::Init() {
path_.string().c_str(),
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
throw NativeFile::Exception {"open failure"};
throw NFile::Exception {"open failure"};
}
handle_ = reinterpret_cast<uintptr_t>(h);
}
NativeFile::~NativeFile() noexcept {
NFile::~NFile() noexcept {
auto h = reinterpret_cast<HANDLE>(handle_);
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_);
LONG off_low = offset & 0xFFFFFFFF;
LONG off_high = offset >> 32;
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;
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);
}
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_);
LONG off_low = offset & 0xFFFFFFFF;
LONG off_high = offset >> 32;
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;
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);
}
size_t NativeFile::Truncate(size_t size) {
size_t NFile::Truncate(size_t size) {
const auto h = reinterpret_cast<HANDLE>(handle_);
LONG off_low = size & 0xFFFFFFFF;
LONG off_high = size >> 32;
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)) {
throw NativeFile::Exception {"SetEndOfFile failure"};
throw NFile::Exception {"SetEndOfFile failure"};
}
return size;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <cassert>
#include <functional>
#include <memory>
#include <mutex>
@ -38,7 +39,7 @@ class NodeRootSelectLambda : public nf7::Node::Lambda,
const auto ks = std::string {k};
if (names_.contains(ks)) {
names_.clear();
auto pro = std::move(*pro_);
auto pro = *std::exchange(pro_, std::nullopt);
lk.unlock();
pro.Return({ks, v});
} else {

102
common/ring_buffer.hh Normal file
View 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
View 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

View File

@ -25,7 +25,6 @@ class Thread final : public nf7::Context,
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner) noexcept :
nf7::Context(env, id), env_(&env), runner_(std::move(runner)) {
}
virtual ~Thread() noexcept = default;
Thread(const Thread&) = delete;
Thread(Thread&&) = delete;
Thread& operator=(const Thread&) = delete;

View File

@ -89,12 +89,12 @@ class TimedWaitQueue final : private TimedQueue<T> {
void Notify() noexcept {
cv_.notify_all();
}
void Wait() noexcept {
void Wait(const auto& dur) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (auto t = next_()) {
cv_.wait_until(k, *t);
} else {
cv_.wait(k);
cv_.wait_for(k, dur);
}
}

23
common/util_algorithm.hh Normal file
View 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

View File

@ -1,6 +1,8 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <functional>
#include <optional>
#include <span>
#include <string>
@ -10,6 +12,18 @@
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 {
std::string_view ret;
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;
}
}
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(
std::string_view v,

View File

@ -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
View 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

View File

@ -1,4 +1,6 @@
#include <algorithm>
#include <atomic>
#include <cinttypes>
#include <memory>
#include <imgui.h>
@ -32,7 +34,11 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
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()) {
}
@ -42,10 +48,11 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
return std::make_unique<AudioContext>(env);
}
void Update() noexcept override;
void UpdateMenu() 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 {
return nf7::InterfaceSelector<
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_;
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,
public std::enable_shared_from_this<AudioContext::Queue> {
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 {
kInitializing,
kInitial,
kReady,
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(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 {
th_->Push(
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;
@ -107,122 +117,99 @@ class AudioContext::Queue final : public nf7::audio::Queue,
Queue& operator=(const 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 {
th_->Push(ctx, std::move(task));
}
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(); }
// thread-safe
ma_context* ctx() const noexcept {
return state() == kReady? &tdata_->ctx: nullptr;
}
private:
Env* const env_;
std::shared_ptr<ThreadData> tdata_;
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 {
if (ImGui::MenuItem("display available devices")) {
popup_ = "DeviceList";
ma_device_info* pbs;
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 {
const auto state = q_->state();
const char* state_str =
state == Queue::kInitializing? "initializing":
state == Queue::kInitial? "initializing":
state == Queue::kReady ? "ready":
state == Queue::kBroken ? "broken": "unknown";
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

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
#include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/luajit.hh"
#include "common/luajit_queue.hh"
#include "common/ptr_selector.hh"
#include "common/queue.hh"
@ -19,7 +20,7 @@ namespace {
class LuaContext final : public nf7::File, public nf7::DirItem {
public:
static inline const GenericTypeInfo<LuaContext> kType = {
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
@ -33,22 +34,20 @@ class LuaContext final : public nf7::File, public nf7::DirItem {
class Queue;
LuaContext(nf7::Env& env) :
File(kType, env), DirItem(DirItem::kTooltip) {
q_ = std::make_shared<Queue>(*this);
}
LuaContext(nf7::Env& 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);
}
void UpdateMenu() 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<
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:
struct Runner final {
Runner(Queue& owner) noexcept : owner_(&owner) {
Runner(std::weak_ptr<Queue> owner) noexcept : owner_(owner) {
}
void operator()(Task&& t) {
t(owner_->L);
if (auto k = owner_.lock()) {
t(k->L);
}
}
private:
Queue* const owner_;
std::weak_ptr<Queue> owner_;
};
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(LuaContext& f) :
L(luaL_newstate()), env_(&f.env()),
th_(std::make_shared<Thread>(f, Runner {*this})) {
if (!L) throw nf7::Exception("failed to create new Lua state");
Queue(LuaContext& f) : L(luaL_newstate()), env_(&f.env()) {
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 {
th_->Push(
@ -100,7 +111,22 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
Env* const env_;
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 {
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
if (q_) {

View File

@ -8,6 +8,8 @@
#include <ImNodes.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
@ -17,9 +19,9 @@
#include "common/file_base.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.hh"
#include "common/gui_config.hh"
#include "common/gui_file.hh"
#include "common/gui_node.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit_queue.hh"
@ -28,6 +30,7 @@
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/util_algorithm.hh"
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 =
{"LuaJIT/InlineNode", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
ImGui::Bullet();
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
ImGui::TextUnformatted("Defines new pure Node without creating nfile.");
}
class Lambda;
struct Data {
Data() noexcept { }
std::string Stringify() const noexcept;
void Parse(const std::string&);
std::string script;
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 :
nf7::FileBase(kType, env, {&socket_popup_}),
nf7::DirItem(nf7::DirItem::kWidget),
nf7::FileBase(kType, env, {}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this) {
nf7::FileBase::Install(*log_);
socket_popup_.onSubmit = [this](auto&& i, auto&& o) {
this->data().inputs = std::move(i);
this->data().outputs = std::move(o);
mem_.Commit();
mem_.onCommit = mem_.onRestore = [this]() {
cache_ = std::nullopt;
};
}
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 {
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 {
return std::make_unique<InlineNode>(env, Data {data()});
return std::make_unique<InlineNode>(env, 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 data().inputs;
return mem_->inputs;
}
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 UpdateNode(nf7::Node::Editor&) 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_;
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,
public std::enable_shared_from_this<InlineNode::Lambda> {
public:
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,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
file_.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;
}
f_.EnforceAlive();
auto self = shared_from_this();
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq,
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
th->Install(log_);
th_.emplace_back(th);
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);
f_->Build().
ThenIf(self, [this, k = std::string {k}, v, caller](auto& func) mutable {
if (f_) StartThread(std::move(k), v, func, caller);
}).
Catch<nf7::Exception>([log = log_](auto&) {
log->Warn("skips execution because of build failure");
});
} 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();
}
}
} catch (nf7::ExpiredException&) {
}
private:
// synchronized with filesystem
nf7::Life<InlineNode>::Ref file_;
nf7::Life<InlineNode>::Ref f_;
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
std::optional<nf7::luajit::Ref> func_;
std::optional<nf7::luajit::Ref> ctxtable_;
void StartThread(std::string&& k, const nf7::Value& v,
const std::shared_ptr<nf7::luajit::Ref>& func,
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(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
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 {
if (ImGui::MenuItem("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
}
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
@ -229,32 +220,66 @@ void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
ImGui::TextUnformatted("LuaJIT/InlineNode");
ImGui::SameLine();
if (ImGui::SmallButton("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
if (ImGui::SmallButton("config")) {
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::InputTextMultiline("##script", &data().script, {24*em, 8*em});
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
nf7::gui::NodeOutputSockets(data().outputs);
socket_popup_.Update();
nf7::gui::NodeOutputSockets(mem_->outputs);
}
void InlineNode::UpdateWidget() noexcept {
ImGui::TextUnformatted("LuaJIT/InlineNode");
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
nf7::gui::Config(mem_);
}
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 (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
if (nf7::util::Uniq(new_outputs) > 0) {
throw nf7::Exception {"duplicated outputs"};
}
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()};
}
}

View File

@ -1,117 +1,117 @@
#include <algorithm>
#include <exception>
#include <memory>
#include <filesystem>
#include <mutex>
#include <optional>
#include <typeinfo>
#include <variant>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <lua.hpp>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_config.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/nfile_watcher.hh"
#include "common/node.hh"
#include "common/node_root_lambda.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;
namespace nf7 {
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:
static inline const GenericTypeInfo<Node> kType =
static inline const nf7::GenericTypeInfo<LuaNode> kType =
{"LuaJIT/Node", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Defines new Node using Lua object factory.");
ImGui::Bullet();
ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj");
ImGui::TextUnformatted("defines new pure Node");
}
class Builder;
class Lambda;
struct Meta {
std::vector<std::string> inputs, outputs;
std::optional<nf7::luajit::Ref> lambda;
};
struct Data {
nf7::FileHolder::Tag obj;
std::string desc;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
std::string Stringify() const noexcept;
void Parse(const std::string&);
std::filesystem::path npath;
};
Node(Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&obj_, &obj_editor_, &socket_popup_}),
LuaNode(Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&nfile_watcher_}),
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kNone),
life_(*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) {
nf7::FileBase::Install(*log_);
mem_.data().obj.SetTarget(obj_);
mem_.CommitAmend();
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;
nfile_watcher_.onMod = [this]() {
cache_ = std::nullopt;
};
}
Node(nf7::Deserializer& ar) : Node(ar.env()) {
ar(obj_, data().desc, data().inputs, data().outputs);
nf7::util::Uniq(data().inputs);
nf7::util::Uniq(data().outputs);
LuaNode(nf7::Deserializer& ar) : LuaNode(ar.env()) {
ar(mem_->npath);
}
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 {
return std::make_unique<Node>(env, Data {data()});
return std::make_unique<LuaNode>(env, 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 data().inputs;
if (cache_ && cache_->done()) return cache_->value()->inputs;
return {};
}
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;
@ -123,182 +123,196 @@ class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
}
private:
nf7::Life<Node> life_;
nf7::Life<LuaNode> life_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::FileHolder obj_;
nf7::gui::FileHolderEditor obj_editor_;
nf7::gui::IOSocketListPopup socket_popup_;
NFileWatcher nfile_watcher_;
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
// factory context
std::shared_ptr<nf7::NodeRootLambda> factory_;
std::optional<nf7::Future<nf7::Value>> fu_;
std::optional<nf7::Future<std::shared_ptr<Meta>>> cache_;
};
class Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Node::Lambda> {
class LuaNode::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<LuaNode::Lambda> {
public:
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
Lambda(LuaNode& 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>& caller) noexcept override
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(
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
th_.end());
auto ljq = func->ljq();
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq,
nf7::luajit::Thread::CreateNodeLambdaHandler(caller, shared_from_this()));
th->Install(log_);
auto self = shared_from_this();
f_.EnforceAlive();
f_->Build().
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);
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
ljq->Push(self, [this, ctx, ljq, k = std::move(k), v = std::move(v), caller, func, th](auto L) mutable {
auto thL = th->Init(L);
func->PushSelf(thL);
// push args
lua_pushstring(thL, k.c_str());
nf7::luajit::PushValue(thL, v);
// push context table
if (ctxtable_ && ctxtable_->ljq() != ljq) {
ctxtable_ = std::nullopt;
ljq->Push(self, [this, ljq, th, meta, k = std::move(k), v](auto L) {
// create context table
{
std::unique_lock<std::mutex> _(mtx_);
if (!ctx_ || ctx_->ljq() != ljq) {
lua_createtable(L, 0, 0);
ctx_.emplace(shared_from_this(), ljq, L);
}
if (!ctxtable_) {
lua_createtable(thL, 0, 0);
lua_pushvalue(thL, -1);
ctxtable_.emplace(ctx, ljq, thL);
} else {
ctxtable_->PushSelf(thL);
}
// execute
th->Resume(thL, 3);
// start thread
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> Node::CreateLambda(
std::shared_ptr<nf7::Node::Lambda> LuaNode::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
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:");
ImGui::Indent();
for (const auto& name : data().inputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (data().inputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
nf7::Future<std::shared_ptr<LuaNode::Meta>> LuaNode::Build() noexcept {
if (cache_) return *cache_;
ImGui::Text("output:");
ImGui::Indent();
for (const auto& name : data().outputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (data().outputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "LuaJIT Node builder");
nf7::Future<std::shared_ptr<Meta>>::Promise pro {ctx};
try {
auto ljq =
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
ImGui::Text("description:");
ImGui::Indent();
if (data().desc.empty()) {
ImGui::TextDisabled("(empty)");
} else {
ImGui::TextUnformatted(data().desc.c_str());
auto handler = nf7::luajit::Thread::CreatePromiseHandler<std::shared_ptr<Meta>>(pro, [ctx, ljq](auto L) {
if (1 != lua_gettop(L) || !lua_istable(L, 1)) {
throw nf7::Exception {"builder script should return a table"};
}
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});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
void LuaNode::UpdateTooltip() noexcept {
ImGui::Text("cache : %s", cache_? "ready": "none");
if (cache_ && cache_->done()) {
auto cache = cache_->value();
ImGui::TextUnformatted("inputs:");
for (const auto& name : cache->inputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
ImGui::TextUnformatted("outputs:");
for (const auto& name : cache->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()};
}
}

View File

@ -36,6 +36,7 @@
#include "common/node_link_store.hh"
#include "common/ptr_selector.hh"
#include "common/squashed_history.hh"
#include "common/util_algorithm.hh"
#include "common/yas_imgui.hh"
#include "common/yas_imnodes.hh"
#include "common/yas_nf7.hh"

View File

@ -201,7 +201,7 @@ try {
ssla_ = nullptr;
la_ = nullptr;
}
} catch (nf7::LifeExpiredException&) {
} catch (nf7::ExpiredException&) {
ss->Finish();
} catch (nf7::FileHolder::EmptyException&) {
ss->Finish();

View File

@ -31,6 +31,7 @@
#include "common/gui_timeline.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/memento_recorder.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 = {},
ItemId next = 1,
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::Node(nf7::Node::kMenu_DirItem),
life_(*this),
life_(*this), log_(*this),
layers_(std::move(layers)), next_(next),
win_(*this, "Timeline Editor", win), tl_("timeline"),
popup_add_item_(*this) {
@ -124,6 +125,7 @@ class TL final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
private:
nf7::Life<TL> life_;
nf7::LoggerRef log_;
nf7::SquashedHistory history_;
@ -419,6 +421,14 @@ class TL::Layer final {
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 {
assert(!owner_);
@ -436,19 +446,6 @@ class TL::Layer final {
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 {
auto itr = std::find_if(
items_.begin(), items_.end(),
@ -542,6 +539,26 @@ class TL::Layer final {
// GUI temporary parameters
size_t index_;
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() {
next_ = 1;
@ -765,16 +782,18 @@ class TL::Session final : public Sequencer::Session,
static_cast<nf7::Value::Scalar>(t.dur());
}
};
void TL::Lambda::Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<Node::Lambda>&) noexcept {
if (name == "_exec") {
if (!owner_) return;
uint64_t t;
if (v.isInteger()) {
const auto ti = std::max(v.integer(), int64_t{0});
t = static_cast<uint64_t>(ti);
} else {
// TODO: error
owner_->log_.Error("_exec takes a frame index");
return;
}
CreateSession(t)->StartNext();
@ -805,7 +824,6 @@ class TL::Editor final : public nf7::Sequencer::Editor {
public:
Editor(TL::Item& item) noexcept : item_(&item) {
}
// TODO
private:
TL::Item* const item_;
@ -936,28 +954,15 @@ class TL::Layer::ItemSwapCommand final : public nf7::History::Command {
TL::Item* const ptr_;
void Swap() {
auto& items = layer_->items_;
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_);
items.insert(itr, std::move(item_));
layer_->AddItem(std::move(item_));
} else {
auto itr = std::find_if(items.begin(), items.end(),
[ptr = ptr_](auto& x) { return x.get() == ptr; });
if (itr == items.end()) {
item_ = layer_->RemoveItem(*ptr_);
if (!item_) {
throw nf7::History::CorruptException {"target item missing"};
}
item_ = std::move(*itr);
item_->Detach();
items.erase(itr);
}
}
};
@ -983,8 +988,7 @@ class TL::Layer::ItemTimingSwapCommand final : public nf7::History::Command {
void Exec() noexcept {
std::swap(item_->timing(), timing_);
item_->displayTiming() = item_->timing();
// TODO: reorder item
item_->layer().ReorderItem(*item_);
}
};
void TL::ExecApplyTimingOfSelected() noexcept {
@ -1070,11 +1074,11 @@ class TL::Layer::ItemMoveCommand final : public nf7::History::Command {
src_(&src), dst_(&dst), item_(&item) {
}
void Apply() noexcept override {
src_->MoveItemTo(*dst_, *item_);
dst_->AddItem(src_->RemoveItem(*item_));
item_->MoveTo(*dst_);
}
void Revert() noexcept override {
dst_->MoveItemTo(*src_, *item_);
src_->AddItem(dst_->RemoveItem(*item_));
item_->MoveTo(*src_);
}
@ -1124,8 +1128,11 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept {
layers.emplace_back(item, &layer);
}
for (auto& p : layers) {
p.first->displayLayer().MoveItemTo(*p.second, *p.first);
p.first->DisplayOn(*p.second);
auto& item = *p.first;
auto& src = item.displayLayer();
auto& dst = *p.second;
src.MoveItemTo(item, dst);
item.DisplayOn(dst);
}
}

View File

@ -1,7 +1,6 @@
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <typeinfo>
@ -23,7 +22,8 @@
#include "common/gui_window.hh"
#include "common/life.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/ptr_selector.hh"
#include "common/thread.hh"
@ -33,11 +33,11 @@
namespace nf7 {
namespace {
class NativeFile final : public nf7::FileBase,
class NFile final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<NativeFile> kType = {
"System/NativeFile", {"nf7::DirItem", "nf7::Node"}};
static inline const nf7::GenericTypeInfo<NFile> kType = {
"System/NFile", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Read/Write a file placed on native filesystem.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
@ -46,30 +46,34 @@ class NativeFile final : public nf7::FileBase,
class Lambda;
struct SharedData final {
SharedData(NativeFile& f) noexcept : log(f) {
SharedData(NFile& f) noexcept : log(f) {
}
nf7::LoggerRef log;
std::optional<nf7::NativeFile> nfile;
std::atomic<bool> locked = false;
std::optional<nf7::NFile> nfile;
};
struct Runner final {
struct Task {
std::shared_ptr<NativeFile::Lambda> callee;
std::shared_ptr<nf7::Node::Lambda> caller;
std::shared_ptr<nf7::Node::Lambda> callee, caller;
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 :
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:
std::shared_ptr<SharedData> shared_;
@ -81,7 +85,7 @@ class NativeFile final : public nf7::FileBase,
std::string mode;
};
NativeFile(nf7::Env& env, Data&& data = {}) noexcept :
NFile(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip |
@ -94,18 +98,18 @@ class NativeFile final : public nf7::FileBase,
config_popup_(*this) {
nf7::FileBase::Install(shared_->log);
mem_.onRestore = [this]() { Refresh(); };
mem_.onCommit = [this]() { Refresh(); };
mtx_.onLock = [this]() { SetUp(); };
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);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().npath, data().mode);
}
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(
@ -130,10 +134,11 @@ class NativeFile final : public nf7::FileBase,
}
private:
nf7::Life<NativeFile> life_;
nf7::Life<NFile> life_;
std::shared_ptr<SharedData> shared_;
std::shared_ptr<Thread> th_;
nf7::Mutex mtx_;
std::filesystem::file_time_type lastmod_;
@ -147,7 +152,7 @@ class NativeFile final : public nf7::FileBase,
struct ConfigPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
ConfigPopup(NativeFile& f) noexcept :
ConfigPopup(NFile& f) noexcept :
nf7::gui::Popup("ConfigPopup"), f_(&f) {
}
@ -162,24 +167,35 @@ class NativeFile final : public nf7::FileBase,
void Update() noexcept override;
private:
NativeFile* const f_;
NFile* const f_;
std::string npath_;
bool read_, write_;
} config_popup_;
void Refresh() noexcept {
Runner::Task t;
t.preproc = [](auto& shared) { shared->nfile = std::nullopt; };
th_->Push(std::make_shared<nf7::GenericContext>(*this), std::move(t));
void SetUp() {
const auto& mode = data().mode;
nf7::NFile::Flags flags = 0;
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,
public std::enable_shared_from_this<NativeFile::Lambda> {
class NFile::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<NFile::Lambda> {
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_) {
}
~Lambda() noexcept {
@ -192,20 +208,15 @@ class NativeFile::Lambda final : public nf7::Node::Lambda,
const auto type = v.tuple("type").string();
if (type == "lock") {
Push(caller, [this]() {
Lock();
return nf7::Value::Pulse {};
});
const auto ex = v.tuple("ex").boolean();
Push(caller, ex, []() { return nf7::Value::Pulse {}; });
} else if (type == "unlock") {
Push(caller, [this]() {
shared_->nfile = std::nullopt;
Unlock();
return nf7::Value::Pulse {};
});
lock_ = std::nullopt;
caller->Handle("result", nf7::Value::Pulse {}, shared_from_this());
} else if (type == "read") {
const auto offset = v.tuple("offset").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;
buf.resize(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") {
const auto offset = v.tuple("offset").integer<size_t>();
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());
return nf7::Value {static_cast<nf7::Value::Integer>(ret)};
});
} else if (type == "truncate") {
const auto size = v.tuple("size").integer<size_t>();
Push(caller, [this, size]() {
Push(caller, true, [this, size]() {
shared_->nfile->Truncate(size);
return nf7::Value::Pulse {};
});
@ -232,73 +243,35 @@ class NativeFile::Lambda final : public nf7::Node::Lambda,
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:
nf7::Life<NativeFile>::Ref f_;
nf7::Life<NFile>::Ref f_;
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();
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,
.caller = caller,
.caller = std::move(caller),
.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 {
return std::make_shared<NativeFile::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());
return std::make_shared<NFile::Lambda>(*this, parent);
}
void NativeFile::Update() noexcept {
void NFile::Update() noexcept {
nf7::FileBase::Update();
// file update check
@ -311,24 +284,24 @@ void NativeFile::Update() noexcept {
} catch (std::filesystem::filesystem_error&) {
}
}
void NativeFile::UpdateMenu() noexcept {
void NFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
config_popup_.Open();
}
}
void NativeFile::UpdateTooltip() noexcept {
void NFile::UpdateTooltip() noexcept {
ImGui::Text("npath: %s", data().npath.generic_string().c_str());
ImGui::Text("mode : %s", data().mode.c_str());
}
void NativeFile::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/NativeFile");
void NFile::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/NFile");
if (ImGui::Button("config")) {
config_popup_.Open();
}
config_popup_.Update();
}
void NativeFile::ConfigPopup::Update() noexcept {
void NFile::ConfigPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::InputText("path", &npath_);
ImGui::Checkbox("read", &read_);

472
file/value_plot.cc Normal file
View 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
View File

@ -28,22 +28,84 @@
#include "theme.hh"
using namespace nf7;
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 {
public:
static constexpr auto kFileName = "root.nf7";
static constexpr size_t kSubTaskUnit = 64;
Env() noexcept : nf7::Env(std::filesystem::current_path()) {
// start threads
main_thread_ = std::thread([this]() { MainThread(); });
async_threads_.resize(std::max<size_t>(std::thread::hardware_concurrency(), 2));
for (auto& th : async_threads_) {
th = std::thread([this]() { AsyncThread(); });
}
// deserialize
if (!std::filesystem::exists(kFileName)) {
root_ = CreateRoot(*this);
@ -53,38 +115,34 @@ class Env final : public nf7::Env {
nf7::Deserializer::Load(*this, kFileName, root_);
root_->MakeAsRoot();
} 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;
while (main_.size() || sub_.size() || async_.size()) {
std::this_thread::sleep_for(100ms);
}
}
alive_ = false;
cv_.notify_one();
main_thread_.join();
async_.Notify();
for (auto& th : async_threads_) th.join();
void ExecMain(const std::shared_ptr<nf7::Context>& ctx,
Task&& task) noexcept override {
mainq_.Push({ctx, std::move(task)});
}
void ExecSub(const std::shared_ptr<nf7::Context>& ctx,
Task&& task) noexcept override {
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 {
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
void Handle(const nf7::File::Event& e) noexcept override
try {
// trigger File::Handle()
GetFileOrThrow(e.id).Handle(e);
@ -97,27 +155,23 @@ class Env final : public nf7::Env {
// trigger global watcher
for (auto w : watchers_map_[0]) w->Handle(e);
} catch (ExpiredException&) {
} catch (nf7::ExpiredException&) {
}
void Exit() noexcept override {
exit_requested_ = true;
}
void Save() noexcept override {
void Save() noexcept override
try {
nf7::Serializer::Save(kFileName, root_);
nf7::Serializer::Save(*this, kFileName, root_);
} catch (nf7::Exception&) {
Panic();
panicq_.Push(std::current_exception());
}
}
void Throw(std::exception_ptr&& eptr) noexcept override {
Panic(std::move(eptr));
void Throw(std::exception_ptr&& ptr) noexcept override {
panicq_.Push(std::move(ptr));
}
void Update() noexcept {
interrupt_ = true;
std::unique_lock<std::mutex> _(mtx_);
ImGui::PushID(this);
{
if (root_) {
@ -125,35 +179,31 @@ class Env final : public nf7::Env {
root_->Update();
ImGui::PopID();
}
UpdatePanic();
}
ImGui::PopID();
interrupt_ = false;
cv_.notify_one();
}
bool exitRequested() const noexcept { return exit_requested_; }
protected:
File* GetFile(File::Id id) const noexcept override {
nf7::File* GetFile(nf7::File::Id id) const noexcept override {
auto itr = files_.find(id);
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);
assert(ok);
return itr->first;
}
void RemoveFile(File::Id id) noexcept override {
void RemoveFile(nf7::File::Id id) noexcept override {
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_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]) {
auto& v = watchers_map_[id];
v.erase(std::remove(v.begin(), v.end(), &w), v.end());
@ -162,36 +212,27 @@ class Env final : public nf7::Env {
}
private:
std::unique_ptr<File> root_;
std::atomic<bool> exit_requested_ = false;
std::atomic<bool> alive_ = true;
std::exception_ptr panic_;
File::Id file_next_ = 1;
std::unordered_map<File::Id, File*> files_;
std::unique_ptr<nf7::File> root_;
std::unordered_map<File::Id, std::vector<Watcher*>> watchers_map_;
std::unordered_map<Watcher*, std::vector<File::Id>> watchers_rmap_;
nf7::File::Id file_next_ = 1;
std::unordered_map<nf7::File::Id, nf7::File*> files_;
using TaskItem = std::pair<std::shared_ptr<nf7::Context>, Task>;
nf7::Queue<TaskItem> main_;
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_;
std::unordered_map<nf7::File::Id, std::vector<nf7::Env::Watcher*>> watchers_map_;
std::unordered_map<nf7::Env::Watcher*, std::vector<nf7::File::Id>> watchers_rmap_;
};
void Panic(std::exception_ptr ptr = std::current_exception()) noexcept {
panic_ = ptr;
void UpdatePanic() noexcept {
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);
if (ImGui::BeginPopupModal("panic")) {
ImGui::TextUnformatted("something went wrong X(");
@ -201,11 +242,11 @@ class Env final : public nf7::Env {
const auto kFlags = ImGuiWindowFlags_HorizontalScrollbar;
if (ImGui::BeginChild("panic_detail", size, true, kFlags)) {
auto ptr = panic_;
auto ptr = ptr_;
while (ptr)
try {
std::rethrow_exception(ptr);
} catch (Exception& e) {
} catch (nf7::Exception& e) {
e.UpdatePanic();
ImGui::Separator();
ptr = e.reason();
@ -223,88 +264,50 @@ class Env final : public nf7::Env {
}
ImGui::SameLine();
if (ImGui::Button("ignore")) {
panic_ = {};
ptr_ = {};
ImGui::CloseCurrentPopup();
}
if (const auto rem = panicq_.size()) {
ImGui::SameLine();
ImGui::Text("other %zu exceptions are also causing panic", rem);
}
ImGui::EndPopup();
} 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**) {
// init display
// init GLFW
glfwSetErrorCallback(
[](int, const char* msg) {
std::cout << "GLFW error: " << msg << std::endl;
});
if (!glfwInit()) return 1;
// create window
GLFWwindow* window;
const char* glsl_version;
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_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
# endif
window = glfwCreateWindow(1280, 720, "Nf7", NULL, NULL);
if (window == NULL) return 1;
glfwMakeContextCurrent(window);
glfwSwapInterval(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
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@ -316,40 +319,76 @@ int main(int, char**) {
SetUpImGuiStyle();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
ImGui_ImplOpenGL3_Init("#version 130");
// main logic
{
// main loop
::Env env;
glfwShowWindow(window);
while (!glfwWindowShouldClose(window) && !env.exitRequested()) {
// new frame
// handle events
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_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();
// GUI draw (OpenGL calls occur)
cycle_ = kDraw;
cycle_.notify_all();
glfwMakeContextCurrent(window);
int w, h;
glfwGetFramebufferSize(window, &w, &h);
glViewport(0, 0, w, h);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
env.Save();
glfwMakeContextCurrent(nullptr);
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_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
// teardown display
// tear down display
glfwDestroyWindow(window);
glfwTerminate();
return 0;

4
nf7.cc
View File

@ -293,7 +293,7 @@ Serializer::ChunkGuard::~ChunkGuard() noexcept {
*ar_ & static_cast<uint64_t>(end - begin_);
ar_->st_->Seek(end);
} catch (nf7::Exception&) {
// TODO
ar_->env_->Throw(std::current_exception());
}
}
@ -316,7 +316,7 @@ Deserializer::ChunkGuard::~ChunkGuard() {
ar_->st_->Seek(end);
}
} catch (nf7::Exception&) {
// TODO
ar_->env_->Throw(std::current_exception());
}
}
void Deserializer::ChunkGuard::ValidateEnd() {

9
nf7.hh
View File

@ -383,14 +383,14 @@ class Serializer final :
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"};
Serializer ar {st};
Serializer ar {env, st};
ar(v);
}
Serializer(nf7::SerializerStream& st) :
binary_ostream(st), oarchive_header(st), st_(&st) {
Serializer(nf7::Env& env, nf7::SerializerStream& st) :
binary_ostream(st), oarchive_header(st), env_(&env), st_(&st) {
}
template<typename T>
@ -413,6 +413,7 @@ class Serializer final :
}
private:
nf7::Env* const env_;
nf7::SerializerStream* const st_;
};
class Deserializer final :

View File

@ -117,7 +117,7 @@ target_sources(imnodes
FetchContent_Declare(
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)
@ -177,6 +177,14 @@ endfunction()
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 ----
# repository: https://github.com/mackron/miniaudio
# license : Unlicense
@ -203,6 +211,17 @@ target_include_directories(source_location SYSTEM INTERFACE .)
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 ----
# repository: https://github.com/niXman/yas
# license : Boost