111 Commits

Author SHA1 Message Date
6a4ddb1642 fix memory leaks, and reference of uninitialized value 2022-08-28 19:45:12 +09:00
feb481f00f fix FileHolder issues 2022-08-28 18:31:44 +09:00
69e3c0dcda fix Value/Curve UI 2022-08-28 17:22:08 +09:00
6ad8eb6230 fix issues in LuaJIT builtin library 2022-08-28 16:12:57 +09:00
1b6b2435ed add nf7::DirItem flag to LuaJIT/InlineNode 2022-08-28 15:20:38 +09:00
ea070399fc fix an issue that mouse cursor doesn't change from arrow 2022-08-28 15:06:25 +09:00
df2daddce1 fix an issue that timeline cursor's header is clipped out 2022-08-28 15:05:47 +09:00
1163ee23eb fix issues on Windows 2022-08-27 21:07:33 -07:00
36f95c0c08 improve Sequencer/Timeline UI 2022-08-28 12:58:45 +09:00
9b6eb1a08c fix File::Touch() propagation 2022-08-28 12:27:02 +09:00
beb6ca8ab9 fix indent of theme.hh 2022-08-28 11:19:49 +09:00
622b89820c implement lock system to System/NativeFile 2022-08-28 11:18:53 +09:00
f16042ddf4 fix an issue that cannot rename items of System/Dir 2022-08-28 10:18:48 +09:00
580fcfbdd2 improve ImGui style theme 2022-08-28 02:01:18 +09:00
9b37d2595b fix compile errors on Windows 2022-08-27 09:19:39 -07:00
d41fd1c47b fix issues of LuaJIT/Node 2022-08-28 01:03:33 +09:00
3539181650 fix an issue that LuaJIT::Thread gets into dead locked by using CreateNodeLambdaHandler 2022-08-27 19:02:21 +09:00
b8ddb82e78 add System/Event 2022-08-27 19:01:42 +09:00
b9c3f13f3b add System/Logger/Node 2022-08-27 17:13:01 +09:00
92ae573114 improve Sequencer/Timeline usability 2022-08-27 16:49:04 +09:00
53b4f11620 improve nf7::gui::FileFactory so that can submit by double click type 2022-08-27 15:41:20 +09:00
a93787b43b add System/Call 2022-08-27 12:53:02 +09:00
0c4454f623 fix nf7::GenericHistory destruction to delete commands orderedly 2022-08-27 12:04:31 +09:00
cf771824dc improve nf7::Node interface 2022-08-27 11:42:30 +09:00
901f5c4ab9 implement UpdateWidget() to Node/Imm 2022-08-27 00:31:16 +09:00
a703ba3bbc replace to nf7:yield() from a custom type of Node::Lambda in Lua script 2022-08-27 00:26:30 +09:00
2e25e6d14c add lua.load() and lua.pcall() to lua std library 2022-08-26 23:52:36 +09:00
651bad7625 add MutableMemento and improve FileHolder 2022-08-26 23:13:17 +09:00
bc3143016c improve nf7::GenericMemento 2022-08-26 22:57:00 +09:00
c578721277 allow LuaJIT/InlineNode to have custom I/O 2022-08-26 22:28:20 +09:00
231f67d5dd improve resilience to breaking changes of serialization format 2022-08-26 13:39:07 +09:00
8c25d22955 enhance sanitization of Node/Network 2022-08-25 16:37:56 +09:00
dedd32f2ef implement refreshing of file handler in System/NativeFile 2022-08-25 12:31:18 +09:00
d51fcadf82 fix unhandled exception 2022-08-25 12:31:18 +09:00
8e00af8dab enhance Exception logging 2022-08-25 12:31:18 +09:00
0f655315de implement permanentize of System/NativeFile 2022-08-25 12:31:18 +09:00
4864cbda5e add logger to System/NativeFile 2022-08-25 12:31:18 +09:00
9136b771a7 implement LoggerRef FileBase::Feature interface 2022-08-25 12:31:18 +09:00
07f7df3436 fix textbox size of LuaJIT/InlineNode on Node/Network 2022-08-25 12:31:18 +09:00
227b9fec67 add str() method to Vector on LuaJIT script 2022-08-25 12:31:18 +09:00
1713b11ac8 implement Node interface to System/NativeFile
and remove some useless headers
2022-08-25 12:31:13 +09:00
cec3f79321 implement lua object as Value
remove LuaJIT/Obj
remove nf7::luajit::Obj
add nf7::NodeRootLambda
2022-08-24 19:18:02 +09:00
cdf2760d2f remove unused field 2022-08-24 15:18:48 +09:00
ee0fc93406 fix an issue that LuaJIT/InlineNode causes segfault when parse error 2022-08-24 11:06:48 +09:00
f3b2e67170 add luajit::Ref::PushSelf() 2022-08-24 10:38:19 +09:00
3a0c6f7316 add LuaJIT/InlineNode 2022-08-24 10:27:40 +09:00
7cfbfc6f41 add gui::Window::shownInCurrentFrame() 2022-08-23 23:25:00 +09:00
267241cf8e fix issues on Sequencer/Timeline UI 2022-08-23 23:25:00 +09:00
6640cd219a fix Node/Network/Terminal's memento issue 2022-08-23 16:21:38 +09:00
5e7cd445ff fix an issue that input of Node/Network is not handled properly 2022-08-23 16:18:52 +09:00
ed837c28e4 fix an issue that Memento::Tag remains after AggregateCommand's destruction 2022-08-23 16:18:20 +09:00
8ea003ca96 implement Value/Curve's widget 2022-08-23 15:32:45 +09:00
5f21f0c926 add UpdateConfig() to nf7::DirItem 2022-08-23 15:09:06 +09:00
f90dbc295b add string utililities 2022-08-23 13:33:01 +09:00
43c0e9a887 fix an issue that tag_ remains when deleting file held by FileHolder 2022-08-23 11:19:00 +09:00
0222a41433 remove unused headers 2022-08-22 15:37:39 +09:00
a2faf0a36c fix an issue that command's destructor is called unorderdly 2022-08-22 15:27:40 +09:00
6c052e9d09 add an easy way to copy FileHolder deeply 2022-08-22 15:27:40 +09:00
121209161a replace FileRef to FileHolder 2022-08-22 14:52:15 +09:00
02cc9bf49d fix nf7::Node iface 2022-08-22 12:23:17 +09:00
017fe65f23 fix FileHolder issues 2022-08-21 16:15:52 +09:00
c8efb73853 add Value/Curve 2022-08-21 16:15:44 +09:00
92c1568455 fix an issue that changes of Node/Imm cannot be undone 2022-08-19 23:36:16 +09:00
9e0ad5714f add ExecHandle() and let File::Touch() use it 2022-08-19 23:33:39 +09:00
de4d614e14 add GenericMemento onRestore and onCommit instead of taking File pointer 2022-08-19 23:33:13 +09:00
cfc5596885 improve nf7::gui::Value 2022-08-19 23:12:15 +09:00
dbbaac5c8e rename System/ImGuiConfig -> System/ImGui
and Add root window to use docking feature easily
2022-08-19 22:40:14 +09:00
2c3a5fe182 add 'clear' to context menu of System/Logger 2022-08-18 23:02:30 +09:00
bb9b276e18 replace InputNode and OutputNode to Node/Network/Terminal 2022-08-18 22:37:34 +09:00
b7085b6ec5 implement Sequencer/Timeline's system variable instead of Session::info() 2022-08-18 10:31:12 +09:00
da4e566ef9 fix Audio/Device's ringbuffer not to override already-mixed samples 2022-08-18 09:34:36 +09:00
e46101e2ea allow ExecAsync to take a timeout
and Add time.now() and nf7:sleep() to LuaJIT environment
2022-08-18 09:34:36 +09:00
1afdf14b4d fix compiler warnings in MSVC 2022-08-17 13:10:51 +09:00
3c40cb9eef implement windows version of nf7::NativeFile 2022-08-17 13:10:51 +09:00
2baca5e917 improve luajit interface 2022-08-16 23:56:03 +09:00
845e0a453e improve FileHolderPopup 2022-08-16 16:21:31 +09:00
61bccc3acb add Sequencer/Adaptor 2022-08-16 12:03:04 +09:00
e01039b74e improve Node/Network to use nf7::FileBase 2022-08-15 14:07:49 +09:00
4e3cd1463e implement proper Abort() for composite lambdas 2022-08-15 01:42:57 +09:00
fa6870fa74 fix an issue that Parameter Panel stole a focus of timeline right before moving/resizing items 2022-08-15 01:03:36 +09:00
ba0ef29e64 fix an issue that wrong cursor is displayed at button of Sequencer's custom item 2022-08-15 00:54:08 +09:00
0f5e3c6246 add 'nf7::' prefix to flags of File::TypeInfo 2022-08-15 00:34:34 +09:00
ed970d45c6 add nf7::gui::PopupWrapper<T> 2022-08-15 00:18:52 +09:00
1300256f05 divide GUI codes from nf7::FileHolder 2022-08-15 00:18:12 +09:00
b1fc1a2a7d improve nf7::gui::FileFactory 2022-08-14 22:50:03 +09:00
540ce3bb0c add Sequencer/Call 2022-08-14 19:00:20 +09:00
cec2763e0a add ParameterPanel feature to Sequencer iface 2022-08-14 08:54:58 +09:00
5147f250a1 fix an issue that MementoRecorder doesn't record the first tag. 2022-08-13 13:58:02 +09:00
62d8bf8800 improve Sequencer iface to make it Exception free 2022-08-13 13:58:02 +09:00
9d4bb15b33 allow Node::Lambda to have nf7::Context as a parent 2022-08-13 13:58:02 +09:00
ba9cb13809 fix an issue that kRemove event occurs after removal 2022-08-13 00:49:08 +09:00
d3f245381b degrade History and it's not template now
because there's no need to make command type a template parameter
2022-08-12 14:05:08 +09:00
972f5ce0a1 implement saving children's memento in Sequencer/Timeline 2022-08-12 13:56:41 +09:00
c6308fa2a2 implement Lambda of Sequencer/Timeline 2022-08-12 11:54:18 +09:00
40112d224d move nf7::Lambda to Node::Lambda
and move depth() field to nf7::Context
2022-08-10 23:35:42 +09:00
2156312009 add parent parameter to Context constructor 2022-08-10 22:56:47 +09:00
96e73d3564 add Sequencer/Timeline 2022-08-10 22:38:10 +09:00
e0c79edc55 add nf7::gui::Popup 2022-08-08 10:45:09 +09:00
cfa5a1dd27 add SquashedHistory 2022-08-08 00:17:00 +09:00
1f87138d14 add a dummy parameter to nf7::Node::CreateLambda to avoid issues of multi inheritance 2022-08-07 12:08:23 +09:00
bab7dbba0f improve nf7::Lambda to use name instead of index 2022-08-07 11:47:14 +09:00
604cd8dc5a improve TypeInfo to display tooltip that describes the type 2022-08-06 12:50:40 +09:00
cda00cf237 add GetLambda() to nf7::Node::Editor 2022-08-05 13:57:55 +09:00
1f4d627648 implement real-time update for Node/Imm lambda 2022-08-05 13:49:16 +09:00
03c1199175 improve Lambda interface
also implemet lazy loading in Node/Network
2022-08-05 13:35:31 +09:00
3e8ffb0f72 make type selector large in FileCreatePopup 2022-08-04 22:20:40 +09:00
6f236873d7 fix Node/Network Editor to move unexpectedly after closing context menu 2022-08-04 22:17:41 +09:00
f140c68458 improve Node/Network Editor to move newly-created Node near the mouse cursor 2022-08-04 22:14:40 +09:00
f321c0282f change default type of Node/Imm 2022-08-04 22:09:08 +09:00
5f16a66e98 rename Audio/IO -> Audio/Device 2022-08-04 21:12:25 +09:00
0f2e578134 fix default samplerate of Audio/IO 2022-08-04 21:05:01 +09:00
88 changed files with 9173 additions and 3856 deletions

View File

@@ -23,7 +23,6 @@ set(NF7_CXX_FLAGS
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
add_subdirectory(tool)
# ---- application ----
@@ -40,18 +39,19 @@ target_compile_definitions(nf7
)
target_sources(nf7
PRIVATE
init.hh
main.cc
nf7.cc
nf7.hh
theme.hh
common/aggregate_command.hh
common/async_buffer.hh
common/async_buffer_adaptor.hh
common/audio_queue.hh
common/buffer.hh
common/dir.hh
common/dir_item.hh
common/file_ref.hh
common/file_base.hh
common/file_holder.hh
common/file_holder.cc
common/future.hh
common/generic_context.hh
common/generic_history.hh
@@ -59,54 +59,72 @@ target_sources(nf7
common/generic_type_info.hh
common/generic_watcher.hh
common/gui_dnd.hh
common/gui_context.hh
common/gui_file.hh
common/gui_file.cc
common/gui_node.hh
common/gui_resizer.hh
common/gui_popup.hh
common/gui_popup.cc
common/gui_timeline.hh
common/gui_timeline.cc
common/gui_value.hh
common/gui_value.cc
common/gui_window.hh
common/history.hh
common/lambda.hh
common/lock.hh
common/life.hh
common/logger.hh
common/logger_ref.hh
common/luajit.hh
common/luajit.cc
common/luajit_obj.hh
common/luajit_queue.hh
common/luajit_ref.hh
common/luajit_thread.hh
common/luajit_thread.cc
common/luajit_thread_lambda.hh
common/memento.hh
common/memento_recorder.hh
common/mutable_memento.hh
common/native_file.hh
common/node.hh
common/node_link_store.hh
common/node_root_lambda.hh
common/ptr_selector.hh
common/queue.hh
common/task.hh
common/sequencer.hh
common/squashed_history.hh
common/thread.hh
common/timed_queue.hh
common/util_string.hh
common/value.hh
common/wait_queue.hh
common/yas_audio.hh
common/yas_imgui.hh
common/yas_imnodes.hh
common/yas_nf7.hh
common/yas_std_atomic.hh
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>
file/audio_context.cc
file/audio_io.cc
file/audio_device.cc
file/luajit_context.cc
file/luajit_inline_node.cc
file/luajit_node.cc
file/luajit_obj.cc
file/node_imm.cc
file/node_network.cc
file/node_ref.cc
file/sequencer_adaptor.cc
file/sequencer_call.cc
file/sequencer_timeline.cc
file/system_call.cc
file/system_dir.cc
file/system_imgui_config.cc
file/system_event.cc
file/system_imgui.cc
file/system_logger.cc
file/system_native_file.cc
file/value_curve.cc
)
target_link_libraries(nf7
PRIVATE
@@ -121,14 +139,3 @@ target_link_libraries(nf7
source_location
yas
)
# ---- resource compile ----
set(NF7_DEFAULT_ROOT_INC "${NF7_GENERATED_INCLUDE_DIR}/root.nf7.inc")
add_custom_command(
OUTPUT ${NF7_DEFAULT_ROOT_INC}
DEPENDS nf7-init
COMMAND $<TARGET_FILE:nf7-init> > ${NF7_DEFAULT_ROOT_INC}
COMMENT "generating root.nf7..."
VERBATIM
)
target_sources(nf7 PRIVATE ${NF7_DEFAULT_ROOT_INC})

View File

@@ -1,5 +1,6 @@
#pragma once
#include <functional>
#include <memory>
#include <span>
#include <vector>
@@ -9,45 +10,52 @@
namespace nf7 {
template <typename T = History::Command>
class AggregateCommand : public T {
class AggregateCommand : public nf7::History::Command {
public:
using CommandList = std::vector<std::unique_ptr<T>>;
using CommandList = std::vector<std::unique_ptr<Command>>;
AggregateCommand(CommandList&& commands) noexcept :
commands_(std::move(commands)) {
AggregateCommand(CommandList&& commands, bool applied = false) noexcept :
commands_(std::move(commands)), applied_(applied) {
}
~AggregateCommand() noexcept {
if (applied_) {
for (auto itr = commands_.begin(); itr < commands_.end(); ++itr) {
*itr = nullptr;
}
} else {
for (auto itr = commands_.rbegin(); itr < commands_.rend(); ++itr) {
*itr = nullptr;
}
}
}
void Apply() override {
auto itr = commands_.begin();
try {
try {
while (itr < commands_.end()) {
(*itr)->Apply();
++itr;
}
} catch (History::CorruptException&) {
throw History::CorruptException("failed to apply AggregateCommand");
}
} catch (History::CorruptException&) {
try {
while (itr > commands_.begin()) {
--itr;
(*itr)->Revert();
}
} catch (History::CorruptException&) {
throw History::CorruptException(
"AggregateCommand gave up recovering from failure of apply");
}
throw;
}
Exec(commands_.begin(), commands_.end(),
[](auto& a) { a->Apply(); },
[](auto& a) { a->Revert(); });
applied_ = true;
}
void Revert() override {
auto itr = commands_.rbegin();
Exec(commands_.rbegin(), commands_.rend(),
[](auto& a) { a->Revert(); },
[](auto& a) { a->Apply(); });
applied_ = false;
}
std::span<const std::unique_ptr<Command>> commands() const noexcept { return commands_; }
private:
CommandList commands_;
bool applied_;
static void Exec(auto begin, auto end, const auto& apply, const auto& revert) {
auto itr = begin;
try {
try {
while (itr < commands_.rend()) {
(*itr)->Revert();
while (itr < end) {
apply(*itr);
++itr;
}
} catch (History::CorruptException&) {
@@ -55,9 +63,9 @@ class AggregateCommand : public T {
}
} catch (History::CorruptException&) {
try {
while (itr > commands_.rbegin()) {
while (itr > begin) {
--itr;
(*itr)->Apply();
revert(*itr);
}
} catch (History::CorruptException&) {
throw History::CorruptException(
@@ -66,11 +74,6 @@ class AggregateCommand : public T {
throw;
}
}
std::span<const std::unique_ptr<T>> commands() const noexcept { return commands_; }
private:
CommandList commands_;
};
} // namespace nf7

View File

@@ -1,40 +0,0 @@
#pragma once
#include <cstdint>
#include <exception>
#include <memory>
#include "nf7.hh"
#include "common/buffer.hh"
#include "common/future.hh"
#include "common/lock.hh"
namespace nf7 {
class AsyncBuffer : public nf7::File::Interface, public nf7::Lock::Resource {
public:
using IOException = Buffer::IOException;
AsyncBuffer() = default;
AsyncBuffer(const AsyncBuffer&) = delete;
AsyncBuffer(AsyncBuffer&&) = delete;
AsyncBuffer& operator=(const AsyncBuffer&) = delete;
AsyncBuffer& operator=(AsyncBuffer&&) = delete;
virtual nf7::Future<size_t> Read(size_t offset, uint8_t* buf, size_t size) noexcept = 0;
virtual nf7::Future<size_t> Write(size_t offset, const uint8_t* buf, size_t size) noexcept = 0;
virtual nf7::Future<size_t> Truncate(size_t) noexcept = 0;
virtual nf7::Future<size_t> size() const noexcept = 0;
virtual Buffer::Flags flags() const noexcept = 0;
virtual std::shared_ptr<AsyncBuffer> self(AsyncBuffer* = nullptr) noexcept = 0;
protected:
using nf7::Lock::Resource::OnLock;
using nf7::Lock::Resource::OnUnlock;
};
} // namespace nf7

View File

@@ -1,105 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/buffer.hh"
#include "common/future.hh"
#include "common/queue.hh"
namespace nf7 {
class AsyncBufferAdaptor final :
public nf7::AsyncBuffer, public std::enable_shared_from_this<AsyncBufferAdaptor> {
public:
AsyncBufferAdaptor(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::Buffer>& buf) noexcept :
ctx_(ctx), buf_(buf) {
}
nf7::Future<size_t> Read(size_t offset, uint8_t* ptr, size_t size) noexcept override {
nf7::Future<size_t>::Promise pro(ctx_);
Exec([pro, buf = buf_, offset, ptr, size]() mutable {
pro.Wrap([&]() { return buf->Read(offset, ptr, size); });
});
return pro.future();
}
nf7::Future<size_t> Write(size_t offset, const uint8_t* ptr, size_t size) noexcept override {
nf7::Future<size_t>::Promise pro(ctx_);
Exec([pro, buf = buf_, offset, ptr, size]() mutable {
pro.Wrap([&]() { return buf->Write(offset, ptr, size); });
});
return pro.future();
}
nf7::Future<size_t> Truncate(size_t size) noexcept override {
nf7::Future<size_t>::Promise pro(ctx_);
Exec([pro, buf = buf_, size]() mutable {
pro.Wrap([&]() { return buf->Truncate(size); });
});
return pro.future();
}
nf7::Future<size_t> size() const noexcept override {
nf7::Future<size_t>::Promise pro(ctx_);
const_cast<AsyncBufferAdaptor&>(*this).Exec([pro, buf = buf_]() mutable {
pro.Wrap([&]() { return buf->size(); });
});
return pro.future();
}
Buffer::Flags flags() const noexcept override {
return buf_->flags();
}
std::shared_ptr<AsyncBuffer> self(AsyncBuffer*) noexcept override {
return shared_from_this();
}
protected:
void OnLock() noexcept override {
Exec([buf = buf_]() { return buf->Lock(); });
}
void OnUnlock() noexcept override {
Exec([buf = buf_]() { return buf->Unlock(); });
}
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::Buffer> buf_;
std::mutex mtx_;
bool working_ = false;
nf7::Queue<std::function<void()>> q_;
void Exec(std::function<void()>&& f) noexcept {
q_.Push(std::move(f));
std::unique_lock<std::mutex> k(mtx_);
if (!std::exchange(working_, true)) {
ctx_->env().ExecAsync(
ctx_, [self = shared_from_this()]() { self->Handle(); });
}
}
void Handle() noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (auto task = q_.Pop()) {
k.unlock();
try {
(*task)();
} catch (nf7::Exception&) {
// TODO: unhandled exception :(
}
ctx_->env().ExecAsync(
ctx_, [self = shared_from_this()]() { self->Handle(); });
} else {
working_ = false;
}
}
};
} // namespace nf7

View File

@@ -1,40 +0,0 @@
#pragma once
#include <cstdint>
namespace nf7 {
class Buffer {
public:
enum Flag : uint8_t {
kRead = 1 << 0,
kWrite = 1 << 1,
};
using Flags = uint8_t;
class IOException;
Buffer() = default;
virtual ~Buffer() = default;
Buffer(const Buffer&) = delete;
Buffer(Buffer&&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer& operator=(Buffer&&) = delete;
virtual void Lock() = 0;
virtual void Unlock() = 0;
virtual size_t Read(size_t offset, uint8_t* buf, size_t size) = 0;
virtual size_t Write(size_t offset, const uint8_t* buf, size_t size) = 0;
virtual size_t Truncate(size_t size) = 0;
virtual size_t size() const = 0;
virtual Flags flags() const noexcept = 0;
};
class Buffer::IOException : public nf7::Exception {
public:
using Exception::Exception;
};
} // namespace nf7

View File

@@ -14,7 +14,8 @@ class DirItem : public File::Interface {
kTree = 1 << 0,
kMenu = 1 << 1,
kTooltip = 1 << 2,
kDragDropTarget = 1 << 3,
kWidget = 1 << 3,
kDragDropTarget = 1 << 4,
};
using Flags = uint8_t;
@@ -29,6 +30,7 @@ class DirItem : public File::Interface {
virtual void UpdateTree() noexcept { }
virtual void UpdateMenu() noexcept { }
virtual void UpdateTooltip() noexcept { }
virtual void UpdateWidget() noexcept { }
virtual void UpdateDragDropTarget() noexcept { }
Flags flags() const noexcept { return flags_; }

62
common/file_base.hh Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include <string_view>
#include <vector>
#include "nf7.hh"
namespace nf7 {
class FileBase : public nf7::File {
public:
class Feature {
public:
Feature() = default;
virtual ~Feature() = default;
Feature(const Feature&) = delete;
Feature(Feature&&) = delete;
Feature& operator=(const Feature&) = delete;
Feature& operator=(Feature&&) = delete;
// Feature* is just for avoiding multi inheritance issues with Env::Watcher
virtual nf7::File* Find(std::string_view) const noexcept { return nullptr; }
virtual void Handle(const nf7::File::Event&) noexcept { }
virtual void Update() noexcept { }
};
FileBase(const nf7::File::TypeInfo& t,
nf7::Env& env,
std::vector<Feature*>&& feats = {}) noexcept :
nf7::File(t, env), feats_(std::move(feats)) {
}
nf7::File* Find(std::string_view name) const noexcept override {
for (auto feat : feats_) {
if (auto ret = feat->Find(name)) {
return ret;
}
}
return nullptr;
}
void Handle(const nf7::File::Event& ev) noexcept override {
for (auto feat : feats_) {
feat->Handle(ev);
}
}
void Update() noexcept override {
for (auto feat : feats_) {
feat->Update();
}
}
protected:
void Install(Feature& f) noexcept {
feats_.push_back(&f);
}
private:
std::vector<Feature*> feats_;
};
} // namespace nf7

134
common/file_holder.cc Normal file
View File

@@ -0,0 +1,134 @@
#include "common/file_holder.hh"
#include <imgui.h>
using namespace std::literals;
namespace nf7 {
nf7::File* FileHolder::Find(std::string_view name) const noexcept {
return name == id_? file_: nullptr;
}
void FileHolder::Handle(const nf7::File::Event& ev) noexcept {
switch (ev.type) {
case nf7::File::Event::kAdd:
SetUp();
break;
case nf7::File::Event::kRemove:
TearDown();
break;
default:
break;
}
}
void FileHolder::Update() noexcept {
if (own()) {
ImGui::PushID(this);
file_->Update();
ImGui::PopID();
}
}
void FileHolder::SetUp() noexcept {
const bool first_setup = !file_;
if (own()) {
file_ = std::get<std::shared_ptr<nf7::File>>(entity_).get();
if (owner_->id() && file_->id() == 0) {
file_->MoveUnder(*owner_, id_);
}
} else if (ref()) {
if (owner_->id()) {
try {
file_ = &owner_->ResolveOrThrow(path());
} catch (nf7::File::NotFoundException&) {
file_ = nullptr;
}
}
}
if (file_) {
auto mem = own()? file_->interface<nf7::Memento>(): nullptr;
// init watcher
if (file_->id() && !watcher_) {
watcher_.emplace(file_->env());
watcher_->Watch(file_->id());
watcher_->AddHandler(nf7::File::Event::kUpdate, [this, mem](auto&) {
if (mem) {
auto ptag = std::exchange(tag_, mem->Save());
if (ptag != tag_) {
onChildMementoChange();
if (mem_) mem_->Commit(); // commit owner's memento
}
}
onChildUpdate();
owner_->Touch();
});
}
// memento setup
if (first_setup && mem) {
if (!tag_) {
tag_ = mem->Save();
} else {
mem->Restore(tag_);
}
}
}
}
void FileHolder::TearDown() noexcept {
if (!owner_->id()) return;
if (own()) {
file_->Isolate();
}
file_ = nullptr;
watcher_ = std::nullopt;
}
FileHolder::Tag::Tag(const Tag& src) noexcept {
if (src.target_) {
entity_ = src.target_->entity_;
tag_ = src.target_->tag_;
} else {
entity_ = src.entity_;
tag_ = src.tag_;
}
}
FileHolder::Tag& FileHolder::Tag::operator=(const Tag& src) noexcept {
if (!src.target_ && target_) {
// restore
target_->TearDown();
target_->entity_ = src.entity_;
target_->tag_ = src.tag_;
target_->SetUp();
} else if (!src.target_ && !target_) {
// shallow copy
entity_ = src.entity_;
tag_ = src.tag_;
} else {
assert(false);
}
return *this;
}
void FileHolder::Tag::SetTarget(nf7::FileHolder& h) noexcept {
assert(!target_);
target_ = &h;
h.TearDown();
if (std::holds_alternative<nf7::File::Path>(entity_)) {
h.Emplace(std::move(std::get<nf7::File::Path>(entity_)));
} else if (std::holds_alternative<std::shared_ptr<nf7::File>>(entity_)) {
h.Emplace(std::get<std::shared_ptr<nf7::File>>(entity_)->Clone(h.env()));
}
entity_ = std::monostate {};
tag_ = nullptr;
h.SetUp();
}
} // namespace nf7

188
common/file_holder.hh Normal file
View File

@@ -0,0 +1,188 @@
#pragma once
#include <cassert>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#include <yas/serialize.hpp>
#include <yas/types/std/variant.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_watcher.hh"
#include "common/memento.hh"
#include "common/mutable_memento.hh"
#include "common/yas_nf7.hh"
#include "common/yas_std_variant.hh"
namespace nf7 {
class FileHolder : public nf7::FileBase::Feature {
public:
class Tag;
class EmptyException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
using Entity = std::variant<
std::monostate, nf7::File::Path, std::shared_ptr<nf7::File>>;
FileHolder(nf7::File& owner, std::string_view id,
nf7::MutableMemento* mem = nullptr) noexcept :
owner_(&owner), mem_(mem), id_(id) {
}
FileHolder(nf7::File& owner, std::string_view id,
nf7::MutableMemento& mem) noexcept :
FileHolder(owner, id, &mem) {
}
FileHolder(const FileHolder&) = delete;
FileHolder(FileHolder&&) = delete;
FileHolder& operator=(const FileHolder&) = delete;
FileHolder& operator=(FileHolder&&) = delete;
void Serialize(nf7::Serializer& ar) const {
ar(entity_);
}
void Deserialize(nf7::Deserializer& ar) {
try {
ar(entity_);
} catch (nf7::Exception&) {
entity_ = std::monostate {};
ar.env().Throw(std::current_exception());
}
SetUp();
}
void Emplace(nf7::File::Path&& path) noexcept {
TearDown();
tag_ = nullptr;
entity_ = std::move(path);
SetUp();
onEmplace();
if (mem_) mem_->Commit();
}
void Emplace(std::unique_ptr<nf7::File>&& f) noexcept {
TearDown();
tag_ = nullptr;
entity_ = std::move(f);
SetUp();
onEmplace();
if (mem_) mem_->Commit();
}
nf7::File& GetFileOrThrow() {
if (auto f = GetFile()) {
return *f;
}
throw EmptyException {"holder is empty"};
}
nf7::File* GetFile() noexcept {
SetUp();
return file_;
}
// nf7::FileBase::Feature methods
nf7::File* Find(std::string_view name) const noexcept override;
void Handle(const nf7::File::Event&) noexcept override;
void Update() noexcept override;
bool own() const noexcept {
return std::holds_alternative<std::shared_ptr<nf7::File>>(entity_);
}
bool ref() const noexcept {
return std::holds_alternative<nf7::File::Path>(entity_);
}
bool empty() const noexcept {
return std::holds_alternative<std::monostate>(entity_);
}
nf7::File& owner() const noexcept { return *owner_; }
nf7::Env& env() const noexcept { return owner_->env(); }
const std::string& id() const noexcept { return id_; }
nf7::File* file() const noexcept { return file_; }
nf7::File::Path path() const noexcept {
assert(!empty());
return own()? nf7::File::Path {{id_}}: std::get<nf7::File::Path>(entity_);
}
// called when kUpdate event is happened on the child
std::function<void(void)> onChildUpdate = [](){};
// called when the child's memento tag id is changed
std::function<void(void)> onChildMementoChange = [](){};
// called right before returning from Emplace()
std::function<void(void)> onEmplace = [](){};
private:
nf7::File* const owner_;
nf7::MutableMemento* const mem_;
const std::string id_;
Entity entity_;
std::shared_ptr<nf7::Memento::Tag> tag_;
nf7::File* file_ = nullptr;
std::optional<nf7::GenericWatcher> watcher_;
void SetUp() noexcept;
void TearDown() noexcept;
};
// to save/restore FileHolder's changes through GenericMemento
class FileHolder::Tag final {
public:
Tag() = default;
Tag(const Tag&) noexcept;
Tag& operator=(const Tag&) noexcept;
Tag(Tag&&) = default;
Tag& operator=(Tag&&) = default;
void SetTarget(nf7::FileHolder& h) noexcept;
private:
nf7::FileHolder* target_ = nullptr;
Entity entity_;
std::shared_ptr<nf7::Memento::Tag> tag_;
};
} // namespace nf7
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::FileHolder> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::FileHolder& h) {
h.Serialize(ar);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::FileHolder& h) {
h.Deserialize(ar);
return ar;
}
};
} // namespace yas::detail

View File

@@ -1,89 +0,0 @@
#pragma once
#include <utility>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/yas_nf7.hh"
namespace nf7 {
class FileRef {
public:
FileRef() = delete;
FileRef(File& owner) noexcept : owner_(&owner) {
}
FileRef(File& owner, const File::Path& p, File::Id id = 0) noexcept :
owner_(&owner), path_({p}), id_(id) {
}
FileRef(File& owner, File::Path&& p, File::Id id = 0) noexcept :
owner_(&owner), path_(std::move(p)), id_(id) {
}
FileRef(const FileRef&) = default;
FileRef(FileRef&&) = default;
FileRef& operator=(const FileRef&) = default;
FileRef& operator=(FileRef&&) = default;
File& operator*() const
try {
return owner_->env().GetFileOrThrow(id_);
} catch (ExpiredException&) {
auto& ret = owner_->ResolveOrThrow(path_);
const_cast<File::Id&>(id_) = ret.id();
return ret;
}
FileRef& operator=(const File::Path& path) noexcept {
return operator=(File::Path{path});
}
FileRef& operator=(File::Path&& path) noexcept {
if (path_ != path) {
path_ = std::move(path);
id_ = 0;
}
return *this;
}
const File::Path& path() const noexcept { return path_; }
File::Id id() const { **this; return id_; }
private:
File* owner_;
File::Path path_;
File::Id id_ = 0;
};
} // namespace nf7
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::FileRef> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::FileRef& ref) {
ar(ref.path());
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::FileRef& ref) {
nf7::File::Path path;
ar(path);
ref = path;
return ar;
}
};
} // namespace yas::detail

View File

@@ -35,6 +35,7 @@ class Future final {
class Coro;
using Handle = std::coroutine_handle<Promise>;
using Imm = std::variant<T, std::exception_ptr>;
enum State { kYet, kDone, kError, };
@@ -203,6 +204,10 @@ class Future final {
}
Future(std::exception_ptr e) noexcept : imm_({e}) {
}
Future(const Imm& imm) noexcept : imm_(imm) {
}
Future(Imm&& imm) noexcept : imm_(std::move(imm)) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
@@ -235,7 +240,7 @@ class Future final {
return *this;
}
}
ctx->env().ExecSub(ctx, std::bind(f, Future(data_)));
ctx->env().ExecSub(ctx, std::bind(f, *this));
return *this;
}
@@ -304,7 +309,7 @@ class Future final {
auto await_resume() { return value(); }
private:
std::optional<std::variant<T, std::exception_ptr>> imm_;
std::optional<Imm> imm_;
std::shared_ptr<Data> data_;
Future(const std::shared_ptr<Data>& data) noexcept : data_(data) { }

View File

@@ -10,23 +10,31 @@
namespace nf7 {
template <typename T>
class GenericHistory : public History {
class GenericHistory : public nf7::History {
public:
GenericHistory() = delete;
GenericHistory(Env& env) noexcept : env_(&env) {
}
GenericHistory() = default;
GenericHistory(const GenericHistory&) = delete;
GenericHistory(GenericHistory&&) = default;
GenericHistory& operator=(const GenericHistory&) = delete;
GenericHistory& operator=(GenericHistory&&) = default;
~GenericHistory() noexcept {
Clear();
}
void Add(std::unique_ptr<T>&& cmd) noexcept {
Command& Add(std::unique_ptr<Command>&& cmd) noexcept override {
cmds_.erase(cmds_.begin()+static_cast<intmax_t>(cursor_), cmds_.end());
cmds_.push_back(std::move(cmd));
cursor_++;
return *cmds_.back();
}
void Clear() noexcept {
for (size_t i = 0; i < cursor_; ++i) {
cmds_[i] = nullptr;
}
for (size_t i = cmds_.size(); i > cursor_;) {
--i;
cmds_[i] = nullptr;
}
cmds_.clear();
}
@@ -41,17 +49,15 @@ class GenericHistory : public History {
++cursor_;
}
T* prev() const noexcept {
Command* prev() const noexcept {
return cursor_ > 0? cmds_[cursor_-1].get(): nullptr;
}
T* next() const noexcept {
Command* next() const noexcept {
return cursor_ < cmds_.size()? cmds_[cursor_].get(): nullptr;
}
private:
Env* const env_;
std::vector<std::unique_ptr<T>> cmds_;
std::vector<std::unique_ptr<Command>> cmds_;
size_t cursor_ = 0;
};

View File

@@ -5,18 +5,23 @@
#include <unordered_map>
#include <utility>
#include "common/memento.hh"
#include "nf7.hh"
#include "common/mutable_memento.hh"
namespace nf7 {
template <typename T>
class GenericMemento : public Memento {
class GenericMemento : public nf7::MutableMemento {
public:
class CustomTag;
GenericMemento(File& owner, T&& data) noexcept :
owner_(&owner), initial_(T(data)), data_(std::move(data)) {
GenericMemento(T&& data, nf7::File* f = nullptr) noexcept :
file_(f), initial_(T(data)), data_(std::move(data)) {
}
GenericMemento(T&& data, nf7::File& f) noexcept :
GenericMemento(std::move(data), &f) {
}
~GenericMemento() noexcept {
tag_ = nullptr;
@@ -37,22 +42,21 @@ class GenericMemento : public Memento {
data_ = itr->second;
tag_ = tag;
last_ = tag;
if (owner_->id()) {
owner_->env().Handle(
{.id = owner_->id(), .type = File::Event::kUpdate});
}
onRestore();
if (file_) file_->Touch();
}
void Commit() noexcept {
void Commit() noexcept override {
tag_ = nullptr;
NotifyUpdate();
onCommit();
if (file_) file_->Touch();
}
void CommitAmend() noexcept {
void CommitAmend() noexcept override {
if (!tag_) return;
auto itr = map_.find(tag_->id());
assert(itr != map_.end());
itr->second = data_;
NotifyUpdate();
onCommit();
if (file_) file_->Touch();
}
T& data() noexcept { return data_; }
@@ -66,8 +70,11 @@ class GenericMemento : public Memento {
return itr->second;
}
std::function<void()> onRestore = [](){};
std::function<void()> onCommit = [](){};
private:
File* const owner_;
nf7::File* const file_;
const T initial_;
T data_;
@@ -77,13 +84,6 @@ class GenericMemento : public Memento {
std::shared_ptr<nf7::Memento::Tag> tag_;
std::shared_ptr<nf7::Memento::Tag> last_;
void NotifyUpdate() noexcept {
if (owner_->id()) {
owner_->env().Handle(
{.id = owner_->id(), .type = File::Event::kUpdate});
}
}
};
template <typename T>

View File

@@ -1,35 +1,65 @@
#pragma once
#include <exception>
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <imgui.h>
#include "nf7.hh"
namespace nf7 {
template <typename T>
class GenericTypeInfo : public File::TypeInfo {
concept GenericTypeInfo_UpdateTooltip_ = requires() { T::UpdateTypeTooltip(); };
template <typename T>
concept GenericTypeInfo_Description_ = requires() { T::kTypeDescription; };
template <typename T>
class GenericTypeInfo : public nf7::File::TypeInfo {
public:
GenericTypeInfo(const std::string& name, std::unordered_set<std::string>&& v) noexcept :
TypeInfo(name, AddFlags(std::move(v))) {
}
std::unique_ptr<File> Deserialize(Env& env, Deserializer& d) const override
std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override
try {
return std::make_unique<T>(env, d);
} catch (Exception&) {
throw DeserializeException(std::string(name())+" deserialization failed");
return std::make_unique<T>(ar);
} catch (nf7::Exception&) {
throw nf7::DeserializeException {"deserialization failed"};
} catch (std::exception&) {
throw nf7::DeserializeException {"deserialization failed"};
}
std::unique_ptr<File> Create(Env& env) const noexcept override {
if constexpr (std::is_constructible<T, Env&>::value) {
std::unique_ptr<File> Create(nf7::Env& env) const override {
if constexpr (std::is_constructible<T, nf7::Env&>::value) {
return std::make_unique<T>(env);
} else {
return nullptr;
throw nf7::Exception {name()+" has no factory without parameters"};
}
}
void UpdateTooltip() const noexcept override {
if constexpr (nf7::GenericTypeInfo_UpdateTooltip_<T>) {
T::UpdateTypeTooltip();
} else if constexpr (nf7::GenericTypeInfo_Description_<T>) {
ImGui::TextUnformatted(T::kTypeDescription);
} else {
ImGui::TextUnformatted("(no description)");
}
}
private:
static std::unordered_set<std::string> AddFlags(
std::unordered_set<std::string>&& flags) noexcept {
if (std::is_constructible<T, Env&>::value) flags.insert("File_Factory");
if (std::is_constructible<T, nf7::Env&>::value) {
flags.insert("nf7::File::TypeInfo::Factory");
}
return flags;
}
};

46
common/gui_context.hh Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <cinttypes>
#include <string>
#include <imgui.h>
#include "nf7.hh"
namespace nf7::gui {
inline std::string GetContextDisplayName(const nf7::Context& ctx) noexcept {
auto f = ctx.env().GetFile(ctx.initiator());
const auto initiator =
f? f->abspath().Stringify(): std::string {"<owner missing>"};
char buf[32];
std::snprintf(buf, sizeof(buf), "(0x%0" PRIXPTR ")", reinterpret_cast<uintptr_t>(&ctx));
return initiator + " " + buf;
}
inline std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept {
if (auto parent = ctx.parent()) {
return nf7::gui::GetContextDisplayName(*parent);
} else if (ctx.depth() == 0) {
return "(isolated)";
} else {
return "<owner disappeared> MEMORY LEAK? ;(";
}
}
inline void ContextStack(const nf7::Context& ctx) noexcept {
for (auto p = ctx.parent(); p; p = p->parent()) {
auto f = ctx.env().GetFile(p->initiator());
const auto path = f? f->abspath().Stringify(): "[missing file]";
ImGui::TextUnformatted(path.c_str());
ImGui::TextDisabled("%s", p->GetDescription().c_str());
}
}
} // namespace nf7::gui

View File

@@ -77,7 +77,7 @@ inline void DrawRect() noexcept {
ImGui::GetForegroundDrawList()->AddRect(
r.Min - ImVec2 {3.5f, 3.5f},
r.Max + ImVec2 {3.5f, 3.5f},
ImGui::GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 2.0f);
ImGui::GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);
}
} // namespace nf7::gui::dnd

252
common/gui_file.cc Normal file
View File

@@ -0,0 +1,252 @@
#include "common/gui_file.hh"
#include <cassert>
#include <imgui.h>
#include <imgui_stdlib.h>
#include "common/dir_item.hh"
#include "common/generic_context.hh"
using namespace std::literals;
namespace nf7::gui {
static nf7::DirItem* GetDirItem(nf7::FileHolder& h, nf7::DirItem::Flags f) noexcept
try {
auto& d = h.GetFileOrThrow().interfaceOrThrow<nf7::DirItem>();
return d.flags() & f? &d: nullptr;
} catch (nf7::Exception&) {
return nullptr;
}
bool FileFactory::Update() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::PushItemWidth(16*em);
if (ImGui::IsWindowAppearing()) {
name_ = "new_file";
type_filter_ = "";
}
bool submit = false;
ImGui::InputTextWithHint("##type_filter", "search...", &type_filter_);
if (ImGui::BeginListBox("type", {16*em, 8*em})) {
for (const auto& reg : nf7::File::registry()) {
const auto& t = *reg.second;
const bool match =
t.flags().contains("nf7::File::TypeInfo::Factory") &&
(type_filter_.empty() ||
t.name().find(type_filter_) != std::string::npos) &&
filter_(t);
const bool sel = (type_ == &t);
if (!match) {
if (sel) type_ = nullptr;
continue;
}
constexpr auto kSelectableFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(t.name().c_str(), sel, kSelectableFlags)) {
type_ = &t;
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
t.UpdateTooltip();
ImGui::EndTooltip();
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
submit = true;
}
}
}
ImGui::EndListBox();
}
ImGui::PopItemWidth();
ImGui::Spacing();
if (flags_ & kNameInput) {
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
ImGui::InputText("name", &name_);
ImGui::Spacing();
}
// input validation
bool err = false;
if (type_ == nullptr) {
ImGui::Bullet(); ImGui::TextUnformatted("type is not selected");
err = true;
}
if (flags_ & kNameInput) {
try {
nf7::File::Path::ValidateTerm(name_);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid name: %s", e.msg().c_str());
err = true;
}
if (flags_ & kNameDupCheck) {
if (owner_->Find(name_)) {
ImGui::Bullet(); ImGui::Text("name duplicated");
err = true;
}
}
}
if (!err) {
if (ImGui::Button("ok")) {
submit = true;
}
if (ImGui::IsItemHovered()) {
const auto path = owner_->abspath().Stringify();
if (flags_ & kNameInput) {
ImGui::SetTooltip(
"create %s as '%s' on '%s'", type_->name().c_str(), name_.c_str(), path.c_str());
} else {
ImGui::SetTooltip("create %s on '%s'", type_->name().c_str(), path.c_str());
}
}
}
return submit && !err;
}
std::string FileHolderEditor::GetDisplayText() const noexcept {
std::string text;
if (holder_->own()) {
text = "[OWN] " + holder_->GetFile()->type().name();
} else if (holder_->ref()) {
text = "[REF] "s + holder_->path().Stringify();
} else if (holder_->empty()) {
text = "(empty)";
} else {
assert(false);
}
return text;
}
void FileHolderEditor::Button(float w, bool small) noexcept {
ImGui::PushID(this);
ImGui::BeginGroup();
const auto text = GetDisplayText();
const bool open = small?
ImGui::SmallButton(text.c_str()):
ImGui::Button(text.c_str(), {w, 0});
if (open) {
ImGui::OpenPopup("FileHolderEmplacePopup_FromButton");
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
Tooltip();
ImGui::EndTooltip();
}
ImGui::EndGroup();
UpdateEmplacePopup("FileHolderEmplacePopup_FromButton");
ImGui::PopID();
}
void FileHolderEditor::ButtonWithLabel(const char* name) noexcept {
ImGui::PushID(this);
ImGui::BeginGroup();
Button(ImGui::CalcItemWidth());
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::TextUnformatted(name);
ImGui::EndGroup();
ImGui::PopID();
}
void FileHolderEditor::Tooltip() noexcept {
ImGui::TextUnformatted(GetDisplayText().c_str());
ImGui::Indent();
if (auto a = GetDirItem(*holder_, nf7::DirItem::kTooltip)) {
a->UpdateTooltip();
}
ImGui::Unindent();
}
void FileHolderEditor::ItemWidget(const char* title) noexcept {
if (auto d = GetDirItem(*holder_, nf7::DirItem::kWidget)) {
if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::PushID(d);
ImGui::Indent();
d->UpdateWidget();
ImGui::Unindent();
ImGui::PopID();
}
}
}
void FileHolderEditor::Update() noexcept {
ImGui::PushID(this);
if (std::exchange(open_emplace_, false)) {
ImGui::OpenPopup("FileHolderEmplacePopup_FromMenu");
}
UpdateEmplacePopup("FileHolderEmplacePopup_FromMenu");
ImGui::PopID();
}
void FileHolderEditor::UpdateEmplacePopup(const char* id) noexcept {
if (ImGui::BeginPopup(id)) {
if (ImGui::IsWindowAppearing()) {
if (holder_->ref()) {
type_ = kRef;
path_ = holder_->path().Stringify();
} else {
type_ = kOwn;
path_ = {};
}
}
if (ImGui::RadioButton("own", type_ == kOwn)) { type_ = kOwn; }
ImGui::SameLine();
if (ImGui::RadioButton("ref", type_ == kRef)) { type_ = kRef; }
switch (type_) {
case kOwn:
if (factory_.Update()) {
ImGui::CloseCurrentPopup();
auto& f = holder_->owner();
f.env().ExecMain(
std::make_shared<nf7::GenericContext>(f),
[this]() {
holder_->Emplace(factory_.Create(holder_->owner().env()));
});
}
break;
case kRef:
ImGui::InputText("path", &path_);
bool missing = false;
try {
auto path = nf7::File::Path::Parse(path_);
try {
holder_->owner().ResolveOrThrow(path);
} catch (nf7::File::NotFoundException&) {
missing = true;
}
if (ImGui::Button("apply")) {
ImGui::CloseCurrentPopup();
auto& f = holder_->owner();
f.env().ExecMain(
std::make_shared<nf7::GenericContext>(f),
[this, p = std::move(path)]() mutable {
holder_->Emplace(std::move(p));
});
}
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::TextUnformatted(e.msg().c_str());
}
if (missing) {
ImGui::Bullet(); ImGui::TextUnformatted("the file is missing :(");
}
break;
}
ImGui::EndPopup();
}
}
} // namespace nf7::gui

View File

@@ -1,147 +1,88 @@
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <utility>
#include "nf7.hh"
#include "common/gui_dnd.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
namespace nf7::gui {
enum FileCreatePopupFlag : uint8_t {
kNameInput = 1 << 0,
kNameDupCheck = 1 << 1,
};
template <uint8_t kFlags>
struct FileCreatePopup final {
class FileFactory final {
public:
FileCreatePopup(std::vector<std::string>&& type_flags_and,
std::vector<std::string>&& type_flags_or = {},
std::string_view default_name = "new_file") noexcept :
type_flags_and_(std::move(type_flags_and)),
type_flags_or_(std::move(type_flags_or)),
default_name_(default_name) {
enum Flag : uint8_t {
kNameInput = 1 << 0,
kNameDupCheck = 1 << 1,
};
using Flags = uint8_t;
using Filter = std::function<bool(const nf7::File::TypeInfo&)>;
FileFactory(nf7::File& owner, Filter&& filter, Flags flags = 0) noexcept :
owner_(&owner), filter_(std::move(filter)), flags_(flags) {
}
FileFactory(const FileFactory&) = delete;
FileFactory(FileFactory&&) = default;
FileFactory& operator=(const FileFactory&) = delete;
FileFactory& operator=(FileFactory&&) = delete;
bool Update(nf7::File& owner) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::PushItemWidth(16*em);
if (ImGui::IsWindowAppearing()) {
name_ = default_name_;
type_filter_ = "";
}
if constexpr (kFlags & FileCreatePopupFlag::kNameInput) {
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
ImGui::InputText("name", &name_);
ImGui::Spacing();
}
if (ImGui::BeginListBox("type", {16*em, 4*em})) {
for (const auto& reg : nf7::File::registry()) {
const auto& t = *reg.second;
const auto flag_matcher =
[&t](const auto& flag) { return t.flags().contains(flag); };
const bool flag_and_match = std::all_of(
type_flags_and_.begin(), type_flags_and_.end(), flag_matcher);
const bool flag_or_match =
type_flags_or_.empty() ||
std::any_of(type_flags_or_.begin(), type_flags_or_.end(), flag_matcher);
if (!flag_and_match || !flag_or_match) continue;
const bool name_match =
type_filter_.empty() || t.name().find(type_filter_) != std::string::npos;
const bool sel = (type_ == &t);
if (!name_match) {
if (sel) type_ = nullptr;
continue;
}
constexpr auto kSelectableFlags =
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(t.name().c_str(), sel, kSelectableFlags)) {
type_ = &t;
}
}
ImGui::EndListBox();
}
ImGui::InputTextWithHint("##type_filter", "search...", &type_filter_);
ImGui::PopItemWidth();
ImGui::Spacing();
// input validation
bool err = false;
if (type_ == nullptr) {
ImGui::Bullet(); ImGui::TextUnformatted("type is not selected");
err = true;
}
if constexpr (kFlags & FileCreatePopupFlag::kNameInput) {
try {
nf7::File::Path::ValidateTerm(name_);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid name: %s", e.msg().c_str());
err = true;
}
if constexpr ((kFlags & FileCreatePopupFlag::kNameDupCheck) != 0) {
if (owner.Find(name_)) {
ImGui::Bullet(); ImGui::Text("name duplicated");
err = true;
}
}
}
bool ret = false;
if (!err) {
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
ret = true;
}
if (ImGui::IsItemHovered()) {
const auto path = owner.abspath().Stringify();
if constexpr (kFlags & FileCreatePopupFlag::kNameInput) {
ImGui::SetTooltip(
"create %s as '%s' on '%s'", type_->name().c_str(), name_.c_str(), path.c_str());
} else {
ImGui::SetTooltip("create %s on '%s'", type_->name().c_str(), path.c_str());
}
}
}
return ret;
bool Update() noexcept;
std::unique_ptr<nf7::File> Create(nf7::Env& env) noexcept {
return type_? type_->Create(env): nullptr;
}
const std::string& name() const noexcept { return name_; }
const nf7::File::TypeInfo& type() const noexcept { return *type_; }
private:
std::vector<std::string> type_flags_and_;
std::vector<std::string> type_flags_or_;
std::string default_name_;
nf7::File* const owner_;
const Filter filter_;
const Flags flags_;
std::string name_;
const nf7::File::TypeInfo* type_;
const nf7::File::TypeInfo* type_ = nullptr;
std::string type_filter_;
};
class FileHolderEditor final : public nf7::FileBase::Feature {
public:
enum Type {
kOwn,
kRef,
};
inline bool InputFilePath(const char* id, std::string* path) noexcept {
bool ret = ImGui::InputText(id, path, ImGuiInputTextFlags_EnterReturnsTrue);
if (ImGui::BeginDragDropTarget()) {
if (auto str = gui::dnd::Accept<std::string>(gui::dnd::kFilePath)) {
*path = *str;
ret = true;
}
ImGui::EndDragDropTarget();
FileHolderEditor(nf7::FileHolder& h, FileFactory::Filter&& filter) noexcept :
holder_(&h), factory_(h.owner(), std::move(filter)) {
}
return ret;
}
FileHolderEditor(const FileHolderEditor&) = delete;
FileHolderEditor(FileHolderEditor&&) = default;
FileHolderEditor& operator=(const FileHolderEditor&) = delete;
FileHolderEditor& operator=(FileHolderEditor&&) = delete;
std::string GetDisplayText() const noexcept;
void Button(float w = 0, bool = false) noexcept;
void SmallButton() noexcept { Button(0, true); }
void ButtonWithLabel(const char* id) noexcept;
void Tooltip() noexcept;
void ItemWidget(const char*) noexcept;
void Update() noexcept override;
private:
nf7::FileHolder* const holder_;
bool open_emplace_ = false;
Type type_;
FileFactory factory_;
std::string path_;
void UpdateEmplacePopup(const char*) noexcept;
};
} // namespace nf7::gui

View File

@@ -5,10 +5,12 @@
#include <imgui.h>
#include <imgui_internal.h>
#include <ImNodes.h>
namespace nf7::gui {
static inline void NodeSocket() noexcept {
inline void NodeSocket() noexcept {
auto win = ImGui::GetCurrentWindow();
const auto em = ImGui::GetFontSize();
@@ -26,4 +28,39 @@ static inline void NodeSocket() noexcept {
ImGui::Dummy(sz);
}
inline void NodeInputSockets(std::span<const std::string> names) noexcept {
ImGui::BeginGroup();
for (auto& name : names) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(name.c_str());
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
inline void NodeOutputSockets(std::span<const std::string> names) noexcept {
float maxw = 0;
for (auto& name : names) {
maxw = std::max(maxw, ImGui::CalcTextSize(name.c_str()).x);
}
ImGui::BeginGroup();
for (auto& name : names) {
const auto w = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+maxw-w);
if (ImNodes::BeginOutputSlot(name.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(name.c_str());
ImGui::SameLine();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
}
} // namespacce nf7::gui

46
common/gui_popup.cc Normal file
View File

@@ -0,0 +1,46 @@
#include "common/gui_popup.hh"
#include <imgui_stdlib.h>
#include "nf7.hh"
namespace nf7::gui {
void IOSocketListPopup::Update() noexcept {
if (Popup::Begin()) {
ImGui::InputTextMultiline("inputs", &is_);
ImGui::InputTextMultiline("outputs", &os_);
const auto iterm = nf7::util::SplitAndValidate(is_, nf7::File::Path::ValidateTerm);
const auto oterm = nf7::util::SplitAndValidate(os_, nf7::File::Path::ValidateTerm);
if (iterm) {
ImGui::Bullet();
ImGui::Text("invalid input name: %.*s", (int) iterm->size(), iterm->data());
}
if (oterm) {
ImGui::Bullet();
ImGui::Text("invalid output name: %.*s", (int) oterm->size(), oterm->data());
}
ImGui::Bullet();
ImGui::TextDisabled("duplicated names are removed automatically");
if (!iterm && !oterm && ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
std::vector<std::string> iv, ov;
nf7::util::SplitAndAppend(iv, is_);
nf7::util::Uniq(iv);
nf7::util::SplitAndAppend(ov, os_);
nf7::util::Uniq(ov);
onSubmit(std::move(iv), std::move(ov));
}
ImGui::EndPopup();
}
}
} // namespace nf7::gui

64
common/gui_popup.hh Normal file
View File

@@ -0,0 +1,64 @@
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include <utility>
#include <imgui.h>
#include "common/file_base.hh"
#include "common/util_string.hh"
namespace nf7::gui {
class Popup {
public:
Popup(const char* name, ImGuiWindowFlags flags = 0) noexcept :
name_(name), flags_(flags) {
}
void Open(ImGuiPopupFlags flags = 0) noexcept {
open_flags_ = flags;
}
bool Begin() noexcept {
if (auto flags = std::exchange(open_flags_, std::nullopt)) {
ImGui::OpenPopup(name_, *flags);
}
return ImGui::BeginPopup(name_, flags_);
}
private:
const char* name_;
ImGuiWindowFlags flags_;
std::optional<ImGuiPopupFlags> open_flags_;
};
class IOSocketListPopup final :
public nf7::FileBase::Feature, private Popup {
public:
IOSocketListPopup(const char* name = "IOSocketListPopup",
ImGuiWindowFlags flags = 0) noexcept :
Popup(name, flags) {
}
void Open(std::span<const std::string> iv,
std::span<const std::string> ov) noexcept {
is_ = "";
nf7::util::JoinAndAppend(is_, iv);
os_ = "";
nf7::util::JoinAndAppend(os_, ov);
Popup::Open();
}
void Update() noexcept override;
std::function<void(std::vector<std::string>&&, std::vector<std::string>&&)> onSubmit =
[](auto&&, auto&&){};
private:
std::string is_, os_;
};
} // namespace nf7::gui

View File

@@ -1,49 +0,0 @@
#pragma once
#include <algorithm>
#include <imgui.h>
namespace nf7::gui {
bool Resizer(ImVec2* size, const ImVec2& min, const ImVec2& max, float scale,
const char* idstr = "##resizer") noexcept {
const auto id = ImGui::GetID(idstr);
size->x = std::clamp(size->x, min.x, max.x);
size->y = std::clamp(size->y, min.y, max.y);
auto ctx = ImGui::GetCurrentContext();
auto& io = ImGui::GetIO();
const auto base = ImGui::GetCursorScreenPos();
const auto pos = base + *size*scale;
const auto rc = ImRect {pos.x-1*scale, pos.y-1*scale, pos.x, pos.y};
bool hovered, held;
const bool ret = ImGui::ButtonBehavior(rc, id, &hovered, &held,
ImGuiButtonFlags_FlattenChildren |
ImGuiButtonFlags_PressedOnClickRelease);
if (hovered || held) ctx->MouseCursor = ImGuiMouseCursor_ResizeNESW;
ImGuiCol col = ImGuiCol_Button;
if (hovered) col = ImGuiCol_ButtonHovered;
if (held) {
col = ImGuiCol_ButtonActive;
*size = (io.MousePos + (ImVec2{scale, scale}-ctx->ActiveIdClickOffset) - base) / scale;
size->x = std::clamp(size->x, min.x, max.x);
size->y = std::clamp(size->y, min.y, max.y);
}
const auto newpos = base + *size*scale;
auto dlist = ImGui::GetWindowDrawList();
dlist->AddTriangleFilled(
newpos, newpos+ImVec2{0, -scale}, newpos+ImVec2{-scale, 0},
ImGui::GetColorU32(col));
return ret;
}
} // namespace nf7::gui

390
common/gui_timeline.cc Normal file
View File

@@ -0,0 +1,390 @@
#include "common/gui_timeline.hh"
#include <cassert>
#include <cmath>
#include <string>
#include <utility>
#include <iostream>
#include <imgui_internal.h>
namespace nf7::gui {
bool Timeline::Begin() noexcept {
assert(frame_state_ == kRoot);
layer_idx_ = 0;
layer_y_ = 0;
layer_h_ = 0;
layer_idx_first_display_ = 0;
layer_offset_y_.clear();
scroll_x_to_mouse_ = false;
scroll_y_to_mouse_ = false;
action_ = kNone;
action_target_ = nullptr;
if (!ImGui::BeginChild(id_, {0, 0}, false, ImGuiWindowFlags_NoMove)) {
return false;
}
body_offset_ = {headerWidth(), xgridHeight()};
body_size_ = ImGui::GetContentRegionMax() - body_offset_;
scroll_size_.x = std::max(body_size_.x, GetXFromTime(len_) + 16*ImGui::GetFontSize());
constexpr auto kFlags =
ImGuiWindowFlags_NoScrollWithMouse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoBackground;
ImGui::SetCursorPos({body_offset_.x, 0});
if (ImGui::BeginChild("xgrid", {body_size_.x, body_offset_.y}, false, kFlags)) {
UpdateXGrid();
}
ImGui::EndChild();
ImGui::SetCursorPos({0, body_offset_.y});
if (ImGui::BeginChild("layers", {0, 0}, false, kFlags)) {
frame_state_ = kHeader;
ImGui::BeginGroup();
return true;
}
ImGui::EndChild();
return false;
}
void Timeline::End() noexcept {
assert(frame_state_ == kRoot);
ImGui::EndChild(); // end of root
}
void Timeline::NextLayerHeader(Layer layer, float height) noexcept {
assert(frame_state_ == kHeader);
assert(height > 0);
const auto em = ImGui::GetFontSize();
if (layer_h_ > 0) {
++layer_idx_;
layer_y_ += layer_h_+padding()*2;
}
layer_h_ = height*em;
layer_ = layer;
// save Y offset of the layer if shown
if (layer_idx_first_display_) {
if (layer_y_ < scroll_.y+body_size_.y) {
layer_offset_y_.push_back(layer_y_);
}
} else {
const auto bottom = layer_y_+layer_h_;
if (bottom > scroll_.y) {
layer_idx_first_display_ = layer_idx_;
layer_offset_y_.push_back(layer_y_);
}
}
const auto mouse = ImGui::GetMousePos().y;
if (layerTopScreenY() <= mouse && mouse < layerBottomScreenY()) {
mouse_layer_ = layer;
mouse_layer_y_ = layer_y_;
mouse_layer_h_ = layer_h_;
}
ImGui::SetCursorPos({0, std::round(layer_y_)});
const auto col = ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);
const auto spos = ImGui::GetCursorScreenPos();
const auto size = ImGui::GetWindowSize();
auto d = ImGui::GetWindowDrawList();
d->AddLine({spos.x, spos.y}, {spos.x+size.x, spos.y}, col);
ImGui::SetCursorPos({0, std::round(layer_y_+padding())});
}
bool Timeline::BeginBody() noexcept {
assert(frame_state_ == kHeader);
const auto em = ImGui::GetFontSize();
const auto ctx = ImGui::GetCurrentContext();
const auto& io = ImGui::GetIO();
// end of header group
ImGui::EndGroup();
scroll_size_.y = ImGui::GetItemRectSize().y;
if (ImGui::IsItemHovered()) {
if (auto wh = ImGui::GetIO().MouseWheel) {
scroll_.y -= wh * 5*em;
}
}
// beginnign of body
constexpr auto kFlags = ImGuiWindowFlags_NoBackground;
ImGui::SameLine(0, 0);
if (ImGui::BeginChild("body", {0, scroll_size_.y}, false, kFlags)) {
frame_state_ = kBody;
body_screen_offset_ = ImGui::GetCursorScreenPos();
ImGui::InvisibleButton(
"viewport-grip", scroll_size_,
ImGuiButtonFlags_MouseButtonMiddle |
ImGuiButtonFlags_MouseButtonLeft);
ImGui::SetItemAllowOverlap();
if (ImGui::IsItemActive()) {
switch (ctx->ActiveIdMouseButton) {
case ImGuiMouseButton_Left: // click timeline to set time
action_time_ = GetTimeFromScreenX(io.MousePos.x);
if (ImGui::IsItemActivated() || action_time_ != action_last_set_time_) {
action_ = kSetTime;
action_last_set_time_ = action_time_;
}
break;
case ImGuiMouseButton_Middle: // easyscroll
scroll_ -= io.MouseDelta;
break;
default:
break;
}
}
len_ = 0;
layer_ = nullptr;
layer_idx_ = 0;
layer_y_ = 0;
layer_h_ = 0;
return true;
}
return false;
}
void Timeline::EndBody() noexcept {
assert(frame_state_ == kBody);
frame_state_ = kRoot;
const auto& io = ImGui::GetIO();
const auto em = ImGui::GetFontSize();
// manipulation by mouse
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
if (io.MouseWheel) {
if (io.KeyCtrl) {
const auto xscroll_base = scroll_.x/zoom_;
// zoom
const auto zmin = 16.f / static_cast<float>(len_);
zoom_ += (zoom_*.99f+.01f)*.1f*io.MouseWheel;
zoom_ = std::clamp(zoom_, zmin, 1.f);
scroll_.x = xscroll_base * zoom_;
} else {
// x-scrolling
scroll_.x -= io.MouseWheel * 2*em;
}
}
}
// move x scroll to the mouse
if (scroll_x_to_mouse_) {
const auto x = ImGui::GetMousePos().x-body_screen_offset_.x;
if (x < scroll_.x+2*em) {
scroll_.x = x-2*em;
} else {
const auto right = scroll_.x+body_size_.x - 2*em;
if (x > right) {
scroll_.x += x-right;
}
}
}
scroll_.x = std::clamp(scroll_.x, 0.f, std::max(0.f, scroll_size_.x-body_size_.x));
ImGui::SetScrollX(scroll_.x);
ImGui::EndChild();
// move y scroll to the mouse
if (scroll_y_to_mouse_ && mouse_layer_) {
if (mouse_layer_y_ < scroll_.y) {
scroll_.y = mouse_layer_y_;
} else {
const auto bottom = mouse_layer_y_+mouse_layer_h_;
if (bottom > scroll_.y+body_size_.y) {
scroll_.y = bottom-body_size_.y;
}
}
}
scroll_.y = std::clamp(scroll_.y, 0.f, std::max(0.f, scroll_size_.y-body_size_.y));
ImGui::SetScrollY(scroll_.y);
ImGui::EndChild(); // end of layers
}
bool Timeline::NextLayer(Layer layer, float height) noexcept {
assert(frame_state_ == kBody);
assert(height > 0);
const auto em = ImGui::GetFontSize();
if (layer_h_ > 0) {
++layer_idx_;
layer_y_ += layer_h_+padding()*2;
}
layer_h_ = height*em;
layer_ = layer;
// it's shown if y offset is saved
return !!layerTopY(layer_idx_);
}
bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept {
assert(frame_state_ == kBody);
frame_state_ = kItem;
len_ = std::max(len_, end);
item_ = item;
const auto em = ImGui::GetFontSize();
const auto pad = padding();
const auto left = GetXFromTime(begin);
const auto right = GetXFromTime(end);
const auto w = std::max(1.f, right-left);
const auto h = layer_h_;
ImGui::SetCursorPos({left, std::round(layer_y_+pad)});
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0, 0});
constexpr auto kFlags =
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse;
const bool shown = ImGui::BeginChild(ImGui::GetID(item), {w, h}, true, kFlags);
ImGui::PopStyleVar(1);
if (shown) {
const auto resizer_w = std::min(1*em, w/2);
ImGui::SetCursorPos({0, 0});
ImGui::InvisibleButton("begin", {resizer_w, h});
ImGui::SetItemAllowOverlap();
HandleGrip(item, 0, kResizeBegin, kResizeBeginDone, ImGuiMouseCursor_ResizeEW);
ImGui::SetCursorPos({w-resizer_w, 0});
ImGui::InvisibleButton("end", {resizer_w, h});
ImGui::SetItemAllowOverlap();
HandleGrip(item, -resizer_w, kResizeEnd, kResizeEndDone, ImGuiMouseCursor_ResizeEW);
const auto mover_w = std::max(1.f, w-resizer_w*2);
ImGui::SetCursorPos({resizer_w, 0});
ImGui::InvisibleButton("mover", {mover_w, h});
ImGui::SetItemAllowOverlap();
HandleGrip(item, resizer_w, kMove, kMoveDone, ImGuiMouseCursor_Hand);
const auto wpad = ImGui::GetStyle().WindowPadding / 2;
ImGui::SetCursorPosY(wpad.y);
ImGui::Indent(wpad.x);
}
return shown;
}
void Timeline::EndItem() noexcept {
assert(frame_state_ == kItem);
frame_state_ = kBody;
ImGui::Unindent();
ImGui::EndChild();
}
void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept {
const auto d = ImGui::GetWindowDrawList();
const auto spos = ImGui::GetWindowPos();
const auto size = ImGui::GetWindowSize();
const auto grid_h = xgridHeight();
const auto x = GetScreenXFromTime(t);
if (x < body_screen_offset_.x || x > body_screen_offset_.x+body_size_.x) return;
d->AddLine({x, spos.y}, {x, spos.y+size.y}, col);
const auto em = ImGui::GetFontSize();
const auto num = std::to_string(t);
d->AddText({x, spos.y + grid_h*0.1f }, col, num.c_str());
d->AddText({x, spos.y + grid_h*0.1f+em}, col, name);
}
void Timeline::Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept {
const auto d = ImGui::GetWindowDrawList();
const auto em = ImGui::GetFontSize();
const auto x = GetScreenXFromTime(t);
if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return;
const auto y = layerTopScreenY(layer);
if (!y || *y < scroll_.y) return;
d->AddTriangleFilled({x, *y}, {x+em, *y-em/2}, {x+em, *y+em/2}, col);
}
void Timeline::UpdateXGrid() noexcept {
constexpr uint64_t kAccentInterval = 5;
const uint64_t unit_min = static_cast<uint64_t>(1/zoom_);
uint64_t unit = 1;
while (unit < unit_min) unit *= 10;
const auto spos = ImGui::GetWindowPos();
const auto size = ImGui::GetContentRegionMax();
const auto color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
const auto left = GetTimeFromX(scroll_.x)/unit*unit;
const auto right = GetTimeFromX(scroll_.x+body_size_.x)+1;
const auto d = ImGui::GetWindowDrawList();
for (uint64_t t = left; t < right; t += unit) {
const bool accent = !((t/unit)%kAccentInterval);
const auto x = GetScreenXFromTime(t);
const auto y = spos.y + size.y;
const auto h = accent? size.y*0.2f: size.y*0.1f;
d->AddLine({x, y}, {x, y-h}, color);
if (accent) {
const auto num = std::to_string(t);
const auto num_size = ImGui::CalcTextSize(num.c_str());
d->AddText({x - num_size.x/2, y-h - num_size.y}, color, num.c_str());
}
}
}
void Timeline::HandleGrip(Item item, float off, Action ac, Action acdone, ImGuiMouseCursor cur) noexcept {
auto ctx = ImGui::GetCurrentContext();
auto& io = ImGui::GetIO();
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
action_grip_moved_ = false;
} else {
action_ = ac;
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
action_grip_moved_ = true;
}
}
action_target_ = item;
ImGui::SetMouseCursor(cur);
off -= 1;
off += ctx->ActiveIdClickOffset.x;
const auto pos = ImGui::GetMousePos() - ImVec2{off, 0};
action_time_ = GetTimeFromScreenX(pos.x);
scroll_x_to_mouse_ = true;
scroll_y_to_mouse_ = (ac == kMove);
} else {
if (ImGui::IsItemDeactivated()) {
action_ = action_grip_moved_? acdone: kSelect;
action_target_ = item;
}
if (ctx->LastItemData.ID == ctx->HoveredIdPreviousFrame) {
ImGui::SetMouseCursor(cur);
}
}
}
} // namespace nf7::gui

206
common/gui_timeline.hh Normal file
View File

@@ -0,0 +1,206 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <optional>
#include <imgui.h>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "common/yas_imgui.hh"
namespace nf7::gui {
// if (tl.Begin()) {
// tl.NextLayerHeader(layer1, &layer1_height)
// ImGui::Button("layer1");
// tl.NextLayerHeader(layer2, &layer2_height)
// ImGui::Button("layer2");
//
// if (tl.BeginBody()) {
// tl.NextLayer(layer1, &layer);
// if (tl.BeginItem(layer1_item1, 0, 10)) {
// // update item
// }
// tl.EndItem();
// if (tl.BeginItem(layer1_item2, 0, 10)) {
// // update item
// }
// tl.EndItem();
//
// tl.NextLayer(layer2, &layer);
// if (tl.BeginItem(layer2_item1, 0, 10)) {
// // update item
// }
// tl.EndItem();
// if (tl.BeginItem(layer2_item2, 0, 10)) {
// // update item
// }
// tl.EndItem();
// }
// tl_.EndBody();
//
// tl_.Cursor(...);
// tl_.Cursor(...);
//
// // handle actions
// }
// tl.End();
struct Timeline {
public:
enum Action {
kNone,
kSelect,
kResizeBegin,
kResizeBeginDone,
kResizeEnd,
kResizeEndDone,
kMove,
kMoveDone,
kSetTime,
};
using Layer = void*;
using Item = void*;
Timeline() = delete;
Timeline(const char* id) noexcept : id_(id) {
}
Timeline(const Timeline&) = default;
Timeline(Timeline&&) = delete;
Timeline& operator=(const Timeline&) = default;
Timeline& operator=(Timeline&&) = delete;
template <typename Ar>
void serialize(Ar& ar) {
ar(header_width_);
ar(xgrid_height_);
ar(zoom_);
ar(padding_);
ar(scroll_);
}
bool Begin() noexcept;
void End() noexcept;
void NextLayerHeader(Layer layer, float height) noexcept;
bool BeginBody() noexcept;
void EndBody() noexcept;
bool NextLayer(Layer layer, float height) noexcept;
bool BeginItem(Item item, uint64_t begin, uint64_t end) noexcept;
void EndItem() noexcept;
void Cursor(const char*, uint64_t t, uint32_t col) noexcept;
void Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept;
uint64_t GetTimeFromX(float x) const noexcept {
return static_cast<uint64_t>(std::max(0.f, x/ImGui::GetFontSize()/zoom_));
}
uint64_t GetTimeFromScreenX(float x) const noexcept {
return GetTimeFromX(x - body_screen_offset_.x);
}
float GetXFromTime(uint64_t t) const noexcept {
return static_cast<float>(t)*zoom_*ImGui::GetFontSize();
}
float GetScreenXFromTime(uint64_t t) const noexcept {
return GetXFromTime(t)+body_screen_offset_.x;
}
float zoom() const noexcept { return zoom_; }
float headerWidth() const noexcept { return header_width_*ImGui::GetFontSize(); }
float xgridHeight() const noexcept { return xgrid_height_*ImGui::GetFontSize(); }
float padding() const noexcept { return padding_*ImGui::GetFontSize(); }
std::optional<float> layerTopY(size_t idx) noexcept {
if (!layer_idx_first_display_ || idx < *layer_idx_first_display_) {
return std::nullopt;
}
idx -= *layer_idx_first_display_;
if (idx >= layer_offset_y_.size()) {
return std::nullopt;
}
return layer_offset_y_[idx];
}
std::optional<float> layerTopScreenY(size_t idx) noexcept {
auto y = layerTopY(idx);
if (!y) return std::nullopt;
return *y + body_screen_offset_.y;
}
float layerTopScreenY() noexcept {
return body_screen_offset_.y + layer_y_;
}
float layerBottomScreenY() noexcept {
return layerTopScreenY() + layerH() + padding()*2;
}
float layerH() noexcept {
return layer_h_;
}
Layer mouseLayer() const noexcept { return mouse_layer_; }
uint64_t mouseTime() const noexcept {
return GetTimeFromScreenX(ImGui::GetMousePos().x);
}
Action action() const noexcept { return action_; }
Item actionTarget() const noexcept { return action_target_; }
uint64_t actionTime() const noexcept { return action_time_; }
private:
// immutable params
const char* id_;
// permanentized params
float header_width_ = 4.f;
float xgrid_height_ = 4.f;
float zoom_ = 1.f;
float padding_ = 0.2f;
ImVec2 scroll_;
// temporary values (immutable on each frame)
ImVec2 body_size_;
ImVec2 body_offset_;
ImVec2 body_screen_offset_;
// volatile params
enum {kRoot, kHeader, kBody, kItem} frame_state_ = kRoot;
uint64_t len_ = 0;
ImVec2 scroll_size_;
bool scroll_x_to_mouse_;
bool scroll_y_to_mouse_;
Layer mouse_layer_;
float mouse_layer_y_;
float mouse_layer_h_;
Layer layer_;
size_t layer_idx_;
float layer_y_;
float layer_h_;
std::optional<size_t> layer_idx_first_display_;
std::vector<float> layer_offset_y_;
Item item_;
Action action_;
Item action_target_;
uint64_t action_time_;
bool action_grip_moved_;
uint64_t action_last_set_time_ = UINT64_MAX; // for kSetTime
void UpdateXGrid() noexcept;
void HandleGrip(
Item item, float off, Action ac, Action acdone, ImGuiMouseCursor cur) noexcept;
};
} // namespace nf7::gui

115
common/gui_value.cc Normal file
View File

@@ -0,0 +1,115 @@
#include "common/gui_value.hh"
namespace nf7::gui {
bool Value::ReplaceType(Type t) noexcept {
if (type_ == t) return false;
type_ = t;
switch (type_) {
case nf7::gui::Value::kPulse:
entity_ = nf7::Value::Pulse {};
break;
case nf7::gui::Value::kInteger:
entity_ = nf7::Value::Integer {0};
break;
case nf7::gui::Value::kScalar:
case nf7::gui::Value::kNormalizedScalar:
entity_ = nf7::Value::Scalar {0};
break;
case nf7::gui::Value::kString:
case nf7::gui::Value::kMultilineString:
entity_ = nf7::Value::String {};
break;
default:
assert(false);
}
return true;
}
void Value::ValidateValue() const {
bool valid = true;
switch (type_) {
case nf7::gui::Value::kPulse:
valid = entity_.isPulse();
break;
case nf7::gui::Value::kInteger:
valid = entity_.isInteger();
break;
case nf7::gui::Value::kScalar:
case nf7::gui::Value::kNormalizedScalar:
valid = entity_.isScalar();
break;
case nf7::gui::Value::kString:
case nf7::gui::Value::kMultilineString:
valid = entity_.isString();
break;
}
if (!valid) {
throw nf7::DeserializeException {"invalid entity type"};
}
}
bool Value::UpdateTypeButton(const char* name, bool small) noexcept {
if (name == nullptr) {
name = StringifyShortType(type_);
}
if (small) {
ImGui::SmallButton(name);
} else {
ImGui::Button(name);
}
bool ret = false;
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
for (const auto t : kTypes) {
if (ImGui::MenuItem(StringifyType(t), nullptr, type_ == t)) {
ret |= ReplaceType(t);
}
}
ImGui::EndPopup();
}
return ret;
}
bool Value::UpdateEditor() noexcept {
bool ret = false;
const auto w = ImGui::CalcItemWidth();
const auto em = ImGui::GetFontSize();
switch (type_) {
case kPulse:
ImGui::BeginDisabled();
ImGui::Button("PULSE", {w, 0});
ImGui::EndDisabled();
break;
case kInteger:
ImGui::DragScalar("##value", ImGuiDataType_S64, &entity_.integer());
ret |= ImGui::IsItemDeactivatedAfterEdit();
break;
case kScalar:
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
ret |= ImGui::IsItemDeactivatedAfterEdit();
break;
case kNormalizedScalar:
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
ret |= ImGui::IsItemDeactivatedAfterEdit();
break;
case kString:
ImGui::InputTextWithHint("##value", "string", &entity_.string());
ret |= ImGui::IsItemDeactivatedAfterEdit();
break;
case kMultilineString:
ImGui::InputTextMultiline("##value", &entity_.string(), {w, 2.4f*em});
ret |= ImGui::IsItemDeactivatedAfterEdit();
break;
default:
assert(false);
}
return ret;
}
} // namespace nf7::gui

122
common/gui_value.hh Normal file
View File

@@ -0,0 +1,122 @@
#pragma once
#include <cassert>
#include <string>
#include <string_view>
#include <utility>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/value.hh"
namespace nf7::gui {
class Value {
public:
enum Type {
kPulse, kInteger, kScalar, kNormalizedScalar, kString, kMultilineString,
};
static inline const Type kTypes[] = {
kPulse, kInteger, kScalar, kNormalizedScalar, kString, kMultilineString,
};
static const char* StringifyType(Type t) noexcept {
switch (t) {
case kPulse: return "Pulse";
case kInteger: return "Integer";
case kScalar: return "Scalar";
case kNormalizedScalar: return "NormalizedScalar";
case kString: return "String";
case kMultilineString: return "MultilineString";
}
assert(false);
return nullptr;
}
static const char* StringifyShortType(Type t) noexcept {
switch (t) {
case kPulse: return "Pulse";
case kInteger: return "Integer";
case kScalar: return "Scalar";
case kNormalizedScalar: return "NScalar";
case kString: return "String";
case kMultilineString: return "MString";
}
assert(false);
return nullptr;
}
static Type ParseType(std::string_view v) {
return
v == "Pulse"? kPulse:
v == "Integer"? kInteger:
v == "Scalar"? kScalar:
v == "NormalizedScalar"? kNormalizedScalar:
v == "String"? kString:
v == "MultilineString"? kMultilineString:
throw nf7::DeserializeException {"unknown type: "+std::string {v}};
}
Value() = default;
Value(const Value&) = default;
Value(Value&&) = default;
Value& operator=(const Value&) = default;
Value& operator=(Value&&) = default;
bool ReplaceType(Type t) noexcept;
void ReplaceEntity(const nf7::Value& v) {
entity_ = v;
ValidateValue();
}
void ReplaceEntity(nf7::Value&& v) {
entity_ = std::move(v);
ValidateValue();
}
void ValidateValue() const;
bool UpdateTypeButton(const char* name = nullptr, bool small = false) noexcept;
bool UpdateEditor() noexcept;
Type type() const noexcept { return type_; }
const nf7::Value& entity() const noexcept { return entity_; }
private:
Type type_ = kInteger;
nf7::Value entity_ = nf7::Value::Integer {0};
};
} // namespace nf7::gui
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::gui::Value> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::gui::Value& v) {
ar(std::string_view {v.StringifyType(v.type())}, v.entity());
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::gui::Value& v) {
std::string type;
nf7::Value entity;
ar(type, entity);
v.ReplaceType(v.ParseType(type));
v.ReplaceEntity(entity);
return ar;
}
};
} // namespace yas::detail

View File

@@ -15,6 +15,10 @@ namespace nf7::gui {
class Window {
public:
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
return f.abspath().Stringify() + " | " + std::string {name};
}
Window() = delete;
Window(File& owner, std::string_view title, const gui::Window* src = nullptr) noexcept :
owner_(&owner), title_(title),
@@ -25,14 +29,13 @@ class Window {
Window& operator=(const Window&) = delete;
Window& operator=(Window&&) = delete;
bool Begin(const std::function<void()>& before = {}) noexcept {
bool Begin() noexcept {
if (std::exchange(set_focus_, false)) {
ImGui::SetNextWindowFocus();
shown_ = true;
}
if (!shown_) return false;
if (before) before();
need_end_ = true;
return ImGui::Begin(id().c_str(), &shown_);
}
@@ -44,6 +47,7 @@ class Window {
}
void SetFocus() noexcept {
shown_ = true;
set_focus_ = true;
}
@@ -54,7 +58,11 @@ class Window {
}
std::string id() const noexcept {
return owner_->abspath().Stringify() + " | " + title_;
return ConcatId(*owner_, title_);
}
bool shownInCurrentFrame() const noexcept {
return shown_ || set_focus_;
}
bool shown() const noexcept { return shown_; }

View File

@@ -1,7 +1,10 @@
#pragma once
#include <memory>
#include "nf7.hh"
namespace nf7 {
class History {
@@ -16,6 +19,8 @@ class History {
History& operator=(const History&) = delete;
History& operator=(History&&) = delete;
virtual Command& Add(std::unique_ptr<Command>&&) noexcept = 0;
virtual void UnDo() = 0;
virtual void ReDo() = 0;
};
@@ -31,6 +36,13 @@ class History::Command {
virtual void Apply() = 0;
virtual void Revert() = 0;
void ExecApply(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ctx->env().ExecSub(ctx, [this]() { Apply(); });
}
void ExecRevert(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ctx->env().ExecSub(ctx, [this]() { Revert(); });
}
};
class History::CorruptException : public Exception {

View File

@@ -1,64 +0,0 @@
#pragma once
#include <memory>
#include "nf7.hh"
#include "common/value.hh"
namespace nf7 {
class Lambda {
public:
class Owner;
Lambda() = delete;
Lambda(const std::shared_ptr<Owner>& owner) noexcept : owner_(owner) {
}
virtual ~Lambda() = default;
Lambda(const Lambda&) = delete;
Lambda(Lambda&&) = delete;
Lambda& operator=(const Lambda&) = delete;
Lambda& operator=(Lambda&&) = delete;
virtual void Handle(size_t, Value&&, const std::shared_ptr<Lambda>&) noexcept { }
const std::shared_ptr<Owner>& owner() const noexcept { return owner_; }
private:
std::shared_ptr<Owner> owner_;
};
class Lambda::Owner final {
public:
Owner() = delete;
Owner(nf7::File::Path&& path,
std::string_view desc,
const std::shared_ptr<Owner>& parent = nullptr) noexcept :
path_(std::move(path)),
desc_(desc),
depth_(parent? parent->depth()+1: 0),
parent_(parent) {
}
Owner(const Owner&) = delete;
Owner(Owner&&) = delete;
Owner& operator=(const Owner&) = delete;
Owner& operator=(Owner&&) = delete;
const nf7::File::Path& path() const noexcept { return path_; }
const std::string& desc() const noexcept { return desc_; }
size_t depth() const noexcept { return depth_; }
const std::shared_ptr<Owner>& parent() const noexcept { return parent_; }
private:
nf7::File::Path path_;
std::string desc_;
size_t depth_;
std::shared_ptr<Owner> parent_;
};
} // namespace nf7

80
common/life.hh Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include <cassert>
#include <memory>
#include "nf7.hh"
namespace nf7 {
class LifeExpiredException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
template <typename T>
class Life final {
public:
class Ref;
Life() = delete;
Life(T& target) noexcept : ptr_(&target) {
}
~Life() noexcept {
if (data_) data_->ptr = nullptr;
}
Life(const Life&) = delete;
Life(Life&&) = delete;
Life& operator=(const Life&) = delete;
Life& operator=(Life&&) = delete;
private:
T* const ptr_;
struct Data final {
T* ptr;
};
std::shared_ptr<Data> data_;
};
template <typename T>
class Life<T>::Ref final {
public:
Ref() = default;
Ref(const Life& life) noexcept {
if (!life.data_) {
auto& l = const_cast<Life&>(life);
l.data_ = std::make_shared<Data>();
l.data_->ptr = l.ptr_;
}
data_ = life.data_;
}
Ref(const Ref&) = default;
Ref(Ref&&) = default;
Ref& operator=(const Ref&) = default;
Ref& operator=(Ref&&) = default;
void EnforceAlive() const {
if (!data_->ptr) {
throw LifeExpiredException {"target expired"};
}
}
operator bool() const noexcept {
return !!data_->ptr;
}
T& operator*() const noexcept {
assert(data_->ptr);
return *data_->ptr;
}
T* operator->() const noexcept {
return &**this;
}
private:
std::shared_ptr<Data> data_;
};
} // namespace nf7

View File

@@ -1,112 +0,0 @@
#pragma once
#include <cassert>
#include <coroutine>
#include <deque>
#include <exception>
#include <memory>
#include <utility>
#include <vector>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
class Lock final {
public:
class Resource;
class Exception : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
Lock() = default;
Lock(Resource& res, bool ex) noexcept : res_(&res), ex_(ex) {
}
inline ~Lock() noexcept;
Lock(const Lock&) = delete;
Lock(Lock&&) = delete;
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&&) = delete;
void Validate() const {
if (!res_) throw Lock::Exception("target expired");
}
private:
Resource* res_ = nullptr;
bool ex_ = false;
};
class Lock::Resource {
public:
friend Lock;
Resource() = default;
virtual ~Resource() noexcept {
if (auto lock = lock_.lock()) {
lock->res_ = nullptr;
}
for (auto pend : pends_) {
pend.pro.Throw(std::make_exception_ptr<Lock::Exception>({"lock cancelled"}));
}
}
Resource(const Resource&) = delete;
Resource(Resource&&) = delete;
Resource& operator=(const Resource&) = delete;
Resource& operator=(Resource&&) = delete;
nf7::Future<std::shared_ptr<Lock>> AcquireLock(bool ex) noexcept {
if (auto ret = TryAcquireLock(ex)) return ret;
if (ex || pends_.empty() || pends_.back().ex) {
pends_.push_back(ex);
}
return pends_.back().pro.future();
}
std::shared_ptr<Lock> TryAcquireLock(bool ex) noexcept {
if (auto k = lock_.lock()) {
return !ex && !k->ex_ && pends_.empty()? k: nullptr;
}
auto k = std::make_shared<Lock>(*this, ex);
lock_ = k;
OnLock();
return k;
}
protected:
virtual void OnLock() noexcept { }
virtual void OnUnlock() noexcept { }
private:
struct Pending final {
public:
Pending(bool ex_) noexcept : ex(ex_) { }
bool ex;
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
};
std::weak_ptr<Lock> lock_;
std::deque<Pending> pends_;
};
Lock::~Lock() noexcept {
if (!res_) return;
if (res_->pends_.empty()) {
res_->OnUnlock();
return;
}
auto next = std::move(res_->pends_.front());
res_->pends_.pop_front();
auto lock = std::make_shared<Lock>(*res_, next.ex);
res_->lock_ = lock;
next.pro.Return(std::move(lock));
}
} // namespace nf7

View File

@@ -1,5 +1,6 @@
#pragma once
#include <exception>
#include <memory>
#include <string>
#include <string_view>
@@ -44,6 +45,8 @@ struct Logger::Item final {
File::Id file;
std::source_location srcloc;
std::exception_ptr ex;
};
} // namespace nf7

View File

@@ -1,39 +1,51 @@
#pragma once
#include <cassert>
#include <exception>
#include <memory>
#include <mutex>
#include <source_location>
#include <string_view>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/logger.hh"
namespace nf7 {
class LoggerRef final {
class LoggerRef final : public nf7::FileBase::Feature {
public:
LoggerRef() noexcept {
LoggerRef(nf7::File& f, nf7::File::Path&& p = {"_logger"}) noexcept :
file_(&f), path_(std::move(p)) {
}
LoggerRef(const LoggerRef&) = default;
LoggerRef(LoggerRef&&) = default;
LoggerRef& operator=(const LoggerRef&) = default;
LoggerRef& operator=(LoggerRef&&) = default;
void SetUp(nf7::File& f, std::string_view name = "_logger") noexcept {
try {
id_ = f.id();
logger_ = f.ResolveUpwardOrThrow(name).
interfaceOrThrow<nf7::Logger>().self();
} catch (Exception&) {
id_ = 0;
void Handle(const nf7::File::Event& ev) noexcept override {
std::unique_lock<std::mutex> k(mtx_);
switch (ev.type) {
case nf7::File::Event::kAdd:
try {
id_ = file_->id();
logger_ = file_->
ResolveUpwardOrThrow(path_).interfaceOrThrow<nf7::Logger>().self();
} catch (nf7::Exception&) {
id_ = 0;
logger_ = nullptr;
}
break;
case nf7::File::Event::kRemove:
id_ = 0;
logger_ = nullptr;
break;
default:
break;
}
}
void TearDown() noexcept {
id_ = 0;
logger_ = nullptr;
}
// thread-safe
void Trace(std::string_view msg, std::source_location s = std::source_location::current()) noexcept {
@@ -42,21 +54,35 @@ class LoggerRef final {
void Info(std::string_view msg, std::source_location s = std::source_location::current()) noexcept {
Write({nf7::Logger::kInfo, msg, 0, s});
}
void Info(nf7::Exception& e, std::source_location s = std::source_location::current()) noexcept {
Info(e.StringifyRecursive(), s);
}
void Warn(std::string_view msg, std::source_location s = std::source_location::current()) noexcept {
Write({nf7::Logger::kWarn, msg, 0, s});
}
void Warn(nf7::Exception& e, std::source_location s = std::source_location::current()) noexcept {
Warn(e.StringifyRecursive(), s);
}
void Error(std::string_view msg, std::source_location s = std::source_location::current()) noexcept {
Write({nf7::Logger::kError, msg, 0, s});
}
void Error(nf7::Exception& e, std::source_location s = std::source_location::current()) noexcept {
Error(e.StringifyRecursive(), s);
}
void Write(nf7::Logger::Item&& item) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (!id_ || !logger_) return;
item.file = id_;
item.ex = std::current_exception();
logger_->Write(std::move(item));
}
private:
File::Id id_;
nf7::File* const file_;
const nf7::File::Path path_;
std::mutex mtx_;
File::Id id_;
std::shared_ptr<nf7::Logger> logger_;
};

View File

@@ -2,6 +2,7 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cinttypes>
#include <cctype>
#include <string>
@@ -17,8 +18,10 @@
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;
// buffer <-> lua value conversion
template <typename T>
@@ -32,14 +35,29 @@ 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) {
PushValue(L, CheckValue(L, 1));
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");
@@ -52,14 +70,14 @@ void PushGlobalTable(lua_State* L) noexcept {
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::Vector");
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(**imm));
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
} else {
PushMutableVector(L, std::vector<uint8_t> {**imm});
}
@@ -111,7 +129,7 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); }
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); }
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); }
auto operator()(Value::Vector) noexcept { lua_pushnil(L); }
auto operator()(Value::Vector) noexcept { PushVector(L, v.vector()); }
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
auto operator()(Value::Tuple) noexcept {
@@ -142,14 +160,14 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
}
lua_setmetatable(L, -2);
}
void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
assert(v);
new (lua_newuserdata(L, sizeof(v))) nf7::Value::Vector(v);
new (lua_newuserdata(L, sizeof(v))) nf7::Value::ConstVector(v);
if (luaL_newmetatable(L, "nf7::Value::Vector")) {
if (luaL_newmetatable(L, "nf7::Value::ConstVector")) {
lua_createtable(L, 0, 0);
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::Vector>(L, 1, "nf7::Value::Vector");
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
const auto offset = luaL_checkinteger(L, 2);
if (offset < 0) {
return luaL_error(L, "negative offset");
@@ -170,7 +188,7 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
if (lua_istable(L, -1)) { // array
lua_rawgeti(L, -1, 1);
const std::string_view type = luaL_checkstring(L, -1);
lua_rawgeti(L, -1, 2);
lua_rawgeti(L, -2, 2);
const size_t n = static_cast<size_t>(luaL_checkinteger(L, -1));
lua_pop(L, 2);
@@ -229,7 +247,14 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
lua_setfield(L, -2, "get");
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::Vector>(L, 1, "nf7::Value::Vector");
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
lua_pushlstring(L, reinterpret_cast<const char*>(v->data()), v->size());
return 1;
});
lua_setfield(L, -2, "str");
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector");
lua_pushinteger(L, static_cast<lua_Integer>(v->size()));
return 1;
});
@@ -237,7 +262,7 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckRef<nf7::Value::Vector>(L, 1, "nf7::Value::Vector").~shared_ptr();
CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector").~shared_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
@@ -301,6 +326,37 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
return 0;
});
lua_setfield(L, -2, "resize");
lua_pushcfunction(L, [](auto L) {
auto& dst = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
const auto dst_off = luaL_checkinteger(L, 2);
const std::vector<uint8_t>* src;
if (const auto& v = ToVector(L, 3)) {
src = &**v;
} else if (const auto& mv = ToMutableVector(L, 3)) {
src = &*mv;
} else {
return luaL_error(L, "#2 argument must be vector or mutable vector");
}
const auto src_off = luaL_checkinteger(L, 4);
const lua_Integer size = luaL_checkinteger(L, 5);
if (size < 0) {
return luaL_error(L, "negative size");
}
if (dst_off < 0 || static_cast<size_t>(dst_off+size) > dst.size()) {
return luaL_error(L, "dst out of bounds");
}
if (src_off < 0 || static_cast<size_t>(src_off+size) > src->size()) {
return luaL_error(L, "src out of bounds");
}
std::memcpy(dst. data()+static_cast<size_t>(dst_off),
src->data()+static_cast<size_t>(src_off),
static_cast<size_t>(size));
return 0;
});
lua_setfield(L, -2, "blit");
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
@@ -319,8 +375,7 @@ std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
}
if (lua_isnumber(L, idx)) {
const double n = lua_tonumber(L, idx);
const auto i = static_cast<nf7::Value::Integer>(n);
return n == static_cast<double>(i)? nf7::Value {i}: nf7::Value{n};
return nf7::Value {n};
}
if (lua_isboolean(L, idx)) {
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
@@ -356,8 +411,8 @@ std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
}
return std::nullopt;
}
std::optional<nf7::Value::Vector> ToVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<nf7::Value::Vector>(L, idx, "nf7::Value::Vector");
std::optional<nf7::Value::ConstVector> ToVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<nf7::Value::ConstVector>(L, idx, "nf7::Value::ConstVector");
if (!ptr) return std::nullopt;
return *ptr;
}
@@ -368,6 +423,53 @@ std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexc
}
static void PushLuaLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
lua_pushcfunction(L, [](auto L) {
if (lua_toboolean(L, 1)) {
return 0;
}
if (lua_gettop(L) >= 2) {
return luaL_error(L, lua_tostring(L, 2));
} else {
return luaL_error(L, "assertion failure");
}
});
lua_setfield(L, -2, "assert");
lua_pushcfunction(L, [](auto L) {
return luaL_error(L, luaL_checkstring(L, 1));
});
lua_setfield(L, -2, "error");
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));
}
return 1;
});
lua_setfield(L, -2, "load");
lua_pushcfunction(L, [](auto L) {
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
lua_pushboolean(L, true);
lua_insert(L, 1);
return lua_gettop(L);
} else {
lua_pushboolean(L, false);
lua_insert(L, 1);
return 2;
}
});
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);
@@ -414,6 +516,24 @@ static void PushTableLib(lua_State* L) noexcept {
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
static void PushTimeLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// time.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);
lua_pushnumber(L, static_cast<double>(ms.count())/1000.);
return 1;
});
lua_setfield(L, -2, "now");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
template <typename T>
@@ -433,6 +553,7 @@ static size_t PushArrayFromBytes(lua_State* L, size_t n, const uint8_t* ptr, con
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
}
lua_rawseti(L, -2, static_cast<int>(i + 1));
ptr += sizeof(T);
}
return size;
}

View File

@@ -17,11 +17,11 @@ namespace nf7::luajit {
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
void PushValue(lua_State*, const nf7::Value&) noexcept;
void PushVector(lua_State*, const nf7::Value::Vector&) noexcept;
void PushVector(lua_State*, const nf7::Value::ConstVector&) noexcept;
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
std::optional<nf7::Value> ToValue(lua_State*, int) noexcept;
std::optional<nf7::Value::Vector> ToVector(lua_State*, int) noexcept;
std::optional<nf7::Value::ConstVector> ToVector(lua_State*, int) noexcept;
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State*, int) noexcept;

View File

@@ -1,27 +0,0 @@
#pragma once
#include <future>
#include <memory>
#include "nf7.hh"
#include "common/future.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
namespace nf7::luajit {
class Obj : public nf7::File::Interface {
public:
Obj() = default;
Obj(const Obj&) = delete;
Obj(Obj&&) = delete;
Obj& operator=(const Obj&) = delete;
Obj& operator=(Obj&&) = delete;
// result is registered to LUA_REGISTRY
virtual nf7::Future<std::shared_ptr<Ref>> Build() noexcept = 0;
};
} // namespace nf7::luajit

View File

@@ -22,7 +22,8 @@ class Queue : public File::Interface {
Queue& operator=(Queue&&) = delete;
// thread-safe
virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0;
virtual void Push(
const std::shared_ptr<nf7::Context>&, Task&&, nf7::Env::Time t = {}) noexcept = 0;
virtual std::shared_ptr<Queue> self() noexcept = 0;
};

View File

@@ -7,17 +7,22 @@
#include "nf7.hh"
#include "common/luajit_queue.hh"
#include "common/value.hh"
namespace nf7::luajit {
class Ref final {
class Ref final : public nf7::Value::Data {
public:
Ref() = delete;
Ref(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::luajit::Queue>& q, int idx) noexcept :
ctx_(ctx), q_(q), idx_(idx) {
}
Ref(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::luajit::Queue>& q, lua_State* L) noexcept :
ctx_(ctx), q_(q), idx_(luaL_ref(L, LUA_REGISTRYINDEX)) {
}
~Ref() noexcept {
q_->Push(ctx_, [idx = idx_](auto L) { luaL_unref(L, LUA_REGISTRYINDEX, idx); });
}
@@ -26,6 +31,10 @@ class Ref final {
Ref& operator=(const Ref&) = delete;
Ref& operator=(Ref&&) = delete;
void PushSelf(lua_State* L) noexcept {
lua_rawgeti(L, LUA_REGISTRYINDEX, idx_);
}
int index() const noexcept { return idx_; }
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return q_; }

View File

@@ -1,13 +1,10 @@
#include "common/luajit_thread.hh"
#include "common/luajit_thread_lambda.hh"
#include "common/luajit_thread_lock.hh"
#include <chrono>
#include <sstream>
#include <tuple>
#include "common/async_buffer.hh"
#include "common/luajit_obj.hh"
namespace nf7::luajit {
@@ -18,10 +15,6 @@ constexpr size_t kBufferSizeMax = 64 * 1024 * 1024;
// Pushes a metatable for Thread object, available on global table as 'nf7'.
static void PushMeta(lua_State*) noexcept;
// Pushes Lua object built by a file who implements luajit::Obj interface.
static void GetLuaObjAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, File& f);
lua_State* Thread::Init(lua_State* L) noexcept {
assert(state_ == kInitial);
@@ -29,7 +22,7 @@ lua_State* Thread::Init(lua_State* L) noexcept {
th_ = lua_newthread(L);
PushImmEnv(L);
lua_setfenv(L, -2);
th_ref_.emplace(ctx_, ljq_, luaL_ref(L, LUA_REGISTRYINDEX));
th_ref_.emplace(ctx_, ljq_, L);
state_ = kPaused;
return th_;
@@ -81,6 +74,49 @@ void Thread::Abort() noexcept {
}
Thread::Handler Thread::CreateNodeLambdaHandler(
const std::shared_ptr<nf7::Node::Lambda>& caller,
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept {
return [caller, callee](auto& th, auto L) {
switch (th.state()) {
case nf7::luajit::Thread::kPaused:
switch (lua_gettop(L)) {
case 0:
th.ExecResume(L);
return;
case 2:
if (auto v = nf7::luajit::ToValue(L, 2)) {
auto k = luaL_checkstring(L, 1);
caller->env().ExecSub(
caller, [caller, callee, k = std::string {k}, v = std::move(v)]() {
caller->Handle(k, *v, callee);
});
th.ExecResume(L);
return;
} else {
}
/* FALLTHROUGH */
default:
if (auto log = th.logger()) {
log->Warn("invalid use of yield, nf7:yield() or nf7:yield(name, value)");
}
th.ExecResume(L);
return;
}
case nf7::luajit::Thread::kFinished:
return;
default:
if (auto log = th.logger()) {
log->Warn(std::string {"luajit execution error: "}+lua_tostring(L, -1));
}
return;
}
};
}
static void PushMeta(lua_State* L) noexcept {
if (luaL_newmetatable(L, Thread::kTypeName)) {
PushWeakPtrDeleter<Thread>(L);
@@ -106,6 +142,17 @@ static void PushMeta(lua_State* L) noexcept {
});
lua_setfield(L, -2, "resolve");
// nf7:ref(obj)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
lua_pushvalue(L, 2);
auto ref = std::make_shared<nf7::luajit::Ref>(th->ctx(), th->ljq(), L);
PushValue(L, nf7::Value {std::move(ref)});
return 1;
});
lua_setfield(L, -2, "ref");
// nf7:query(file_id, interface)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
@@ -115,13 +162,7 @@ static void PushMeta(lua_State* L) noexcept {
th->env().ExecSub(th->ctx(), [th, L, id, iface = std::move(iface)]() {
try {
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
if (iface == "buffer") {
Thread::Lock<nf7::AsyncBuffer>::AcquireAndPush(L, th, f, false);
} else if (iface == "exbuffer") {
Thread::Lock<nf7::AsyncBuffer>::AcquireAndPush(L, th, f, true);
} else if (iface == "lua") {
GetLuaObjAndPush(L, th, f);
} else if (iface == "node") {
if (iface == "node") {
Thread::Lambda::CreateAndPush(L, th, f);
} else {
throw nf7::Exception {"unknown interface: "+iface};
@@ -135,11 +176,21 @@ static void PushMeta(lua_State* L) noexcept {
});
lua_setfield(L, -2, "query");
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
const auto sec = luaL_checknumber(L, 2);
const auto time = nf7::Env::Clock::now() +
std::chrono::milliseconds(static_cast<uint64_t>(sec*1000));
th->ljq()->Push(th->ctx(), [th, L](auto) { th->ExecResume(L); }, time);
th->ExpectYield(L);
return lua_yield(L, 0);
});
lua_setfield(L, -2, "sleep");
// nf7:yield(results...)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
th->ExecResume(L);
th->ExpectYield(L);
return lua_yield(L, lua_gettop(L)-1);
});
lua_setfield(L, -2, "yield");
@@ -153,7 +204,11 @@ static void PushMeta(lua_State* L) noexcept {
const int n = lua_gettop(L);
std::stringstream st;
for (int i = 2; i <= n; ++i) {
st << lua_tostring(L, i);
if (auto msg = lua_tostring(L, i)) {
st << msg;
} else {
return luaL_error(L, "cannot stringify %s", luaL_typename(L, i));
}
}
logger->Write({lv, st.str()});
return 0;
@@ -171,23 +226,4 @@ static void PushMeta(lua_State* L) noexcept {
}
}
static void GetLuaObjAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, File& f) {
f.interfaceOrThrow<nf7::luajit::Obj>().Build().
Then([th, L](auto fu) {
th->ljq()->Push(th->ctx(), [th, L, fu](auto) mutable {
try {
const auto& obj = fu.value();
if (th->ljq() != obj->ljq()) {
throw nf7::Exception {"the object is built on other LuaJIT context"};
}
lua_rawgeti(L, LUA_REGISTRYINDEX, obj->index());
th->Resume(L, 1);
} catch (nf7::Exception& e) {
th->Resume(L, luajit::PushAll(L, nullptr, e.msg()));
}
});
});
}
} // namespace nf7::luajit

View File

@@ -5,6 +5,7 @@
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@@ -14,10 +15,10 @@
#include "nf7.hh"
#include "common/future.hh"
#include "common/lambda.hh"
#include "common/logger_ref.hh"
#include "common/luajit.hh"
#include "common/luajit_ref.hh"
#include "common/node.hh"
namespace nf7::luajit {
@@ -41,9 +42,14 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// Creates a handler to finalize a promise.
template <typename T>
static Handler CreatePromiseHandler(
static inline Handler CreatePromiseHandler(
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&&) noexcept;
// Creates a handler to emit yielded value to Node::Lambda.
static Handler CreateNodeLambdaHandler(
const std::shared_ptr<nf7::Node::Lambda>& caller,
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept;
// must be called on luajit thread
static std::shared_ptr<Thread> GetPtr(lua_State* L, int idx) {
auto th = CheckWeakPtr<Thread>(L, idx, kTypeName);
@@ -54,9 +60,8 @@ class Thread final : public std::enable_shared_from_this<Thread> {
Thread() = delete;
Thread(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::luajit::Queue>& ljq,
const std::shared_ptr<nf7::Lambda::Owner>& la_owner,
Handler&& handler) noexcept :
ctx_(ctx), ljq_(ljq), la_owner_(la_owner), handler_(std::move(handler)) {
ctx_(ctx), ljq_(ljq), handler_(std::move(handler)) {
}
Thread(const Thread&) = delete;
Thread(Thread&&) = delete;
@@ -115,7 +120,6 @@ class Thread final : public std::enable_shared_from_this<Thread> {
nf7::Env& env() noexcept { return ctx_->env(); }
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; }
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
const std::shared_ptr<nf7::Lambda::Owner>& lambdaOwner() const noexcept { return la_owner_; }
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
State state() const noexcept { return state_; }
@@ -125,7 +129,6 @@ class Thread final : public std::enable_shared_from_this<Thread> {
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
std::shared_ptr<nf7::Lambda::Owner> la_owner_;
Handler handler_;
std::atomic<State> state_ = kInitial;

View File

@@ -48,17 +48,9 @@ class Thread::Lambda final : public Thread::RegistryItem,
private:
std::weak_ptr<Thread> th_;
struct ImmData {
ImmData(std::span<const std::string> i, std::span<const std::string> o) noexcept :
in(i.begin(), i.end()), out(o.begin(), o.end()) {
}
std::vector<std::string> in, out;
};
std::shared_ptr<const ImmData> imm_;
class Receiver;
std::shared_ptr<Receiver> recv_;
std::shared_ptr<nf7::Lambda> la_;
std::shared_ptr<Node::Lambda> la_;
std::shared_ptr<Thread> GetThread(lua_State* L) {
@@ -71,23 +63,23 @@ class Thread::Lambda final : public Thread::RegistryItem,
}
static inline void PushMeta(lua_State* L) noexcept;
static inline size_t GetIndex(lua_State* L, int v, std::span<const std::string> names);
};
// Receives an output from targetted lambda and Resumes the Thread.
class Thread::Lambda::Receiver final : public nf7::Lambda,
class Thread::Lambda::Receiver final : public Node::Lambda,
public std::enable_shared_from_this<Thread::Lambda::Receiver> {
public:
static constexpr size_t kMaxQueue = 1024;
Receiver() = delete;
Receiver(const std::shared_ptr<const Thread::Lambda::ImmData>& imm) noexcept :
nf7::Lambda(nullptr), imm_(imm) {
Receiver(nf7::Env& env, nf7::File::Id id) noexcept :
Node::Lambda(env, id, nullptr) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>&) noexcept override {
values_.emplace_back(idx, std::move(v));
void Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<Node::Lambda>&) noexcept override {
values_.emplace_back(name, v);
if (values_.size() > kMaxQueue) {
values_.pop_front();
}
@@ -97,23 +89,21 @@ class Thread::Lambda::Receiver final : public nf7::Lambda,
// must be called on luajit thread
// Returns true and pushes results to Lua stack when a value is already queued.
bool Select(lua_State* L, const std::shared_ptr<Thread>& th, std::vector<size_t>&& indices) noexcept {
bool Select(lua_State* L,const std::shared_ptr<Thread>& th, std::vector<std::string>&& names) noexcept {
std::unique_lock<std::mutex> k(mtx_);
L_ = L;
th_ = th;
waiting_ = std::move(indices);
waiting_ = std::move(names);
return ResumeIf(false);
}
private:
std::shared_ptr<const Thread::Lambda::ImmData> imm_;
std::deque<std::pair<size_t, Value>> values_;
std::deque<std::pair<std::string, Value>> values_;
std::mutex mtx_;
lua_State* L_;
std::shared_ptr<Thread> th_;
std::vector<size_t> waiting_;
std::vector<std::string> waiting_;
// don't forget to lock mtx_
@@ -123,9 +113,8 @@ class Thread::Lambda::Receiver final : public nf7::Lambda,
Thread::Lambda::Lambda(const std::shared_ptr<Thread>& th, nf7::Node& n) noexcept :
th_(th),
imm_(new ImmData {n.input(), n.output()}),
recv_(new Receiver {imm_}),
la_(n.CreateLambda(th->lambdaOwner())) {
recv_(new Receiver {th->env(), th->ctx()->initiator()}),
la_(n.CreateLambda(recv_)) {
}
void Thread::Lambda::PushMeta(lua_State* L) noexcept {
if (luaL_newmetatable(L, kTypeName)) {
@@ -135,12 +124,12 @@ void Thread::Lambda::PushMeta(lua_State* L) noexcept {
lua_pushcfunction(L, [](auto L) {
auto self = GetPtr(L, 1);
const auto idx = GetIndex(L, 2, self->imm_->in);
const auto val = luajit::CheckValue(L, 3);
auto name = lua_tostring(L, 2);;
auto val = luajit::CheckValue(L, 3);
auto th = self->GetThread(L);
th->env().ExecSub(th->ctx(), [self, th, L, idx, val = std::move(val)]() mutable {
self->la_->Handle(idx, std::move(val), self->recv_);
th->env().ExecSub(th->ctx(), [self, th, L, name = std::move(name), val = std::move(val)]() mutable {
self->la_->Handle(name, std::move(val), self->recv_);
th->ExecResume(L);
});
@@ -153,20 +142,20 @@ void Thread::Lambda::PushMeta(lua_State* L) noexcept {
lua_pushcfunction(L, [](auto L) {
auto self = GetPtr(L, 1);
std::vector<size_t> indices = {};
std::vector<std::string> names = {};
if (lua_istable(L, 2)) {
indices.resize(lua_objlen(L, 2));
for (size_t i = 0; i < indices.size(); ++i) {
names.resize(lua_objlen(L, 2));
for (size_t i = 0; i < names.size(); ++i) {
lua_rawgeti(L, 2, static_cast<int>(i+1));
indices[i] = GetIndex(L, -1, self->imm_->out);
names[i] = lua_tostring(L, -1);
lua_pop(L, 1);
}
} else {
indices.push_back(GetIndex(L, 2, self->imm_->out));
names.push_back(lua_tostring(L, 2));
}
auto th = self->GetThread(L);
if (self->recv_->Select(L, th, std::move(indices))) {
if (self->recv_->Select(L, th, std::move(names))) {
return 2;
} else {
th->ExpectYield(L);
@@ -181,26 +170,6 @@ void Thread::Lambda::PushMeta(lua_State* L) noexcept {
lua_setfield(L, -2, "__gc");
}
}
size_t Thread::Lambda::GetIndex(lua_State* L, int v, std::span<const std::string> names) {
if (lua_isstring(L, v)) {
const char* name = lua_tostring(L, v);
auto itr = std::find(names.begin(), names.end(), name);
if (itr == names.end()) {
luaL_error(L, "unknown input name: %s", name);
}
return static_cast<size_t>(std::distance(names.begin(), itr));
} else {
const auto idx = luaL_checkinteger(L, v);
if (idx < 0) {
luaL_error(L, "index is negative");
}
const auto uidx = static_cast<size_t>(idx);
if (uidx >= names.size()) {
luaL_error(L, "index is too large");
}
return uidx;
}
}
bool Thread::Lambda::Receiver::ResumeIf(bool yielded) noexcept {
@@ -212,12 +181,10 @@ bool Thread::Lambda::Receiver::ResumeIf(bool yielded) noexcept {
continue;
}
const auto self = shared_from_this();
auto v = imm_->out[*itr];
if (yielded) {
th_->ExecResume(L_, std::move(imm_->out[*itr]), p->second);
th_->ExecResume(L_, *itr, p->second);
} else {
luajit::PushAll(L_, std::move(imm_->out[*itr]), p->second);
luajit::PushAll(L_, *itr, p->second);
}
values_.erase(p);
waiting_ = {};

View File

@@ -1,189 +0,0 @@
#pragma once
#include "common/luajit_thread.hh"
#include <memory>
#include <utility>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/lock.hh"
#include "common/luajit.hh"
namespace nf7::luajit {
template <typename T>
class Thread::Lock final : public Thread::RegistryItem,
public std::enable_shared_from_this<Thread::Lock<T>> {
public:
using Res = T;
static void AcquireAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, nf7::File& f, bool ex) {
auto res = f.interfaceOrThrow<Res>().self();
res->AcquireLock(ex).Then([L, th, res](auto fu) {
try {
auto k = std::make_shared<Thread::Lock<Res>>(th, res, fu.value());
th->ljq()->Push(th->ctx(), [L, th, k](auto) {
th->Register(L, k);
k->Push(L);
th->Resume(L, 1);
});
} catch (nf7::Exception& e) {
th->ExecResume(L, nullptr, e.msg());
}
});
}
Lock(const std::shared_ptr<Thread>& th,
const std::shared_ptr<Res>& res,
const std::shared_ptr<nf7::Lock>& lock) :
th_(th), res_(res), lock_(lock) {
}
void Push(lua_State* L) noexcept {
luajit::PushWeakPtr<Thread::Lock<Res>>(L, Thread::Lock<T>::shared_from_this());
PushMeta(L);
lua_setmetatable(L, -2);
}
private:
std::weak_ptr<Thread> th_;
std::shared_ptr<Res> res_;
std::shared_ptr<nf7::Lock> lock_;
auto Validate(lua_State* L) {
auto t = th_.lock();
if (!t) {
luaL_error(L, "thread expired");
}
t->EnsureActive(L);
try {
lock_->Validate();
} catch (nf7::Exception& e) {
luaL_error(L, "%s", e.msg().c_str());
}
return std::make_tuple(t, res_, lock_);
}
static void PushMeta(lua_State* L) noexcept;
};
template <>
void Thread::Lock<nf7::AsyncBuffer>::PushMeta(lua_State* L) noexcept {
constexpr const char* kTypeName = "nf7::luajit::Thread::Lock<nf7::AsyncBuffer>";
constexpr size_t kBufferSizeMax = 1024 * 1024 * 64;
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
// lock:read(offset, bytes [, mutable vector]) -> MutableVector
lua_pushcfunction(L, ([](auto L) {
auto [th, buf, lock] = CheckWeakPtr<Lock>(L, 1, kTypeName)->Validate(L);
auto off = luaL_checkinteger(L, 2);
auto size = luaL_checkinteger(L, 3);
if (off < 0) {
return luaL_error(L, "negative offset");
}
if (size < 0) {
return luaL_error(L, "negative size");
}
const size_t usize = static_cast<size_t>(size);
if (usize > kBufferSizeMax) {
return luaL_error(L, "too large size is requested");
}
// allocates new vector to store result or reuses the passed vector
std::shared_ptr<std::vector<uint8_t>> vec;
if (auto src = ToMutableVector(L, 4)) {
vec = std::make_shared<std::vector<uint8_t>>(std::move(*src));
vec->resize(static_cast<size_t>(size));
} else {
vec = std::make_shared<std::vector<uint8_t>>(size);
}
buf->Read(static_cast<size_t>(off), vec->data(), usize).
Then([th, L, vec](auto fu) {
try {
vec->resize(fu.value());
th->ExecResume(L, std::move(*vec));
} catch (nf7::Exception& e) {
th->ExecResume(L, std::vector<uint8_t> {}, e.msg());
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
}));
lua_setfield(L, -2, "read");
// lock:write(offset, vector) -> size
lua_pushcfunction(L, ([](auto L) {
auto [th, buf, lock] = CheckWeakPtr<Lock>(L, 1, kTypeName)->Validate(L);
auto off = luaL_checkinteger(L, 2);
auto optvec = luajit::ToVector(L, 3);
if (off < 0) {
return luaL_error(L, "negative offset");
}
if (!optvec) {
return luaL_error(L, "vector is expected for the third argument");
}
auto& vec = *optvec;
buf->Write(static_cast<size_t>(off), vec->data(), vec->size()).
Then([th, L, vec](auto fu) {
try {
th->ExecResume(L, fu.value());
} catch (nf7::Exception& e) {
th->ExecResume(L, 0, e.msg());
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
}));
lua_setfield(L, -2, "write");
// lock:truncate(size) -> size
lua_pushcfunction(L, ([](auto L) {
auto [th, buf, lock] = CheckWeakPtr<Lock>(L, 1, kTypeName)->Validate(L);
auto size = luaL_checkinteger(L, 2);
if (size < 0) {
return luaL_error(L, "negative size");
}
buf->Truncate(static_cast<size_t>(size)).
Then([th, L](auto fu) {
try {
th->ExecResume(L, fu.value());
} catch (nf7::Exception& e) {
th->ExecResume(L, nullptr, e.msg());
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
}));
lua_setfield(L, -2, "truncate");
// lock:unlock()
luajit::PushWeakPtrDeleter<Thread::Lock<Res>>(L);
lua_setfield(L, -2, "unlock");
lua_setfield(L, -2, "__index");
luajit::PushWeakPtrDeleter<Lock>(L);
lua_setfield(L, -2, "__gc");
}
}
} // namespace nf7::luajit

View File

@@ -0,0 +1,58 @@
#pragma once
#include <memory>
#include "common/generic_history.hh"
#include "common/memento.hh"
namespace nf7 {
class MementoRecorder final {
public:
MementoRecorder() = delete;
MementoRecorder(nf7::Memento* mem) noexcept :
mem_(mem), tag_(mem? mem->Save(): nullptr) {
}
MementoRecorder(const MementoRecorder&) = delete;
MementoRecorder(MementoRecorder&&) = delete;
MementoRecorder& operator=(const MementoRecorder&) = delete;
MementoRecorder& operator=(MementoRecorder&&) = delete;
std::unique_ptr<nf7::History::Command> CreateCommandIf() noexcept {
if (mem_) {
auto ptag = std::exchange(tag_, mem_->Save());
if (ptag != tag_) {
return std::make_unique<RestoreCommand>(*this, ptag);
}
}
return nullptr;
}
private:
nf7::Memento* const mem_;
std::shared_ptr<nf7::Memento::Tag> tag_;
class RestoreCommand final : public nf7::History::Command {
public:
RestoreCommand(MementoRecorder& rec, const std::shared_ptr<nf7::Memento::Tag>& tag) noexcept :
rec_(&rec), tag_(tag) {
}
void Apply() override { Exec(); }
void Revert() override { Exec(); }
private:
MementoRecorder* const rec_;
std::shared_ptr<nf7::Memento::Tag> tag_;
void Exec() {
auto& mem = *rec_->mem_;
rec_->tag_ = std::exchange(tag_, mem.Save());
mem.Restore(rec_->tag_);
}
};
};
} // namespace nf7

20
common/mutable_memento.hh Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "common/memento.hh"
namespace nf7 {
class MutableMemento : public nf7::Memento {
public:
MutableMemento() = default;
MutableMemento(const MutableMemento&) = delete;
MutableMemento(MutableMemento&&) = delete;
MutableMemento& operator=(const MutableMemento&) = delete;
MutableMemento& operator=(MutableMemento&&) = delete;
virtual void Commit() noexcept = 0;
virtual void CommitAmend() noexcept = 0;
};
} // namespace nf7

View File

@@ -8,55 +8,48 @@
#include "nf7.hh"
#include "common/buffer.hh"
namespace nf7 {
class NativeFile final : public nf7::Buffer, public nf7::Context {
class NativeFile final : public nf7::Context {
public:
class Exception final : public nf7::Exception {
using nf7::Exception::Exception;
};
enum Flag : uint8_t {
kCreateIf = 1 << 0,
kExclusive = 1 << 1,
kTrunc = 1 << 2,
kRead = 1 << 0,
kWrite = 1 << 1,
};
using Flags = uint8_t;
NativeFile() = delete;
NativeFile(nf7::File& f,
const std::filesystem::path& path,
Buffer::Flags flags,
Flags nflags) noexcept :
Context(f.env(), f.id()), path_(path), flags_(flags), nflags_(nflags) {
NativeFile(nf7::Env& env, nf7::File::Id id,
const std::filesystem::path& path, Flags flags) :
Context(env, id), path_(path), flags_(flags) {
Init();
}
~NativeFile() noexcept;
NativeFile(const NativeFile&) = delete;
NativeFile(NativeFile&&) = delete;
NativeFile& operator=(const NativeFile&) = delete;
NativeFile& operator=(NativeFile&&) = delete;
void Lock() override;
void Unlock() override;
size_t Read(size_t offset, uint8_t* buf, size_t size) override;
size_t Write(size_t offset, const uint8_t* buf, size_t size) override;
size_t Truncate(size_t size) override;
size_t Read(size_t offset, uint8_t* buf, size_t size);
size_t Write(size_t offset, const uint8_t* buf, size_t size);
size_t Truncate(size_t size);
size_t size() const override;
Buffer::Flags flags() const noexcept override {
Flags flags() const noexcept {
return flags_;
}
void CleanUp() noexcept override;
void Abort() noexcept override;
size_t GetMemoryUsage() const noexcept override;
std::string GetDescription() const noexcept override;
private:
const std::filesystem::path path_;
const Buffer::Flags flags_;
const NativeFile::Flags nflags_;
const Flags flags_;
std::optional<uint64_t> handle_;
uintptr_t handle_;
void Init();
};
} // namespace nf7

View File

@@ -8,123 +8,62 @@ extern "C" {
#include <unistd.h>
}
#include <thread>
namespace nf7 {
void NativeFile::Lock() {
if (handle_) {
throw nf7::Buffer::IOException("already locked");
}
void NativeFile::Init() {
int flags = 0;
if ((flags_ & nf7::Buffer::kRead) && (flags_ & nf7::Buffer::kWrite)) {
flags |= O_RDWR;
} else if (flags_ & nf7::Buffer::kRead) {
if ((flags_ & kRead) && (flags_ & kWrite)) {
flags |= O_RDWR | O_CREAT;
} else if (flags_ & kRead) {
flags |= O_RDONLY;
} else if (flags_ & nf7::Buffer::kWrite) {
flags |= O_WRONLY;
} else if (flags_ & kWrite) {
flags |= O_WRONLY | O_CREAT;
}
if (nflags_ & kCreateIf) flags |= O_CREAT;
if (nflags_ & kTrunc) flags |= O_TRUNC;
int fd = open(path_.string().c_str(), flags, 0600);
if (fd < 0) {
throw nf7::Buffer::IOException("open failure");
throw NativeFile::Exception {"open failure"};
}
handle_ = static_cast<uint64_t>(fd);
if (nflags_ & kExclusive) {
if (flock(fd, LOCK_EX) != 0) {
close(fd);
throw nf7::Buffer::IOException("flock failure");
}
}
}
void NativeFile::Unlock() {
if (!handle_) {
throw nf7::Buffer::IOException("not locked yet");
}
const auto fd = static_cast<int>(*handle_);
if (nflags_ & kExclusive) {
if (flock(fd, LOCK_UN) != 0) {
close(fd);
throw nf7::Buffer::IOException("flock failure");
}
}
NativeFile::~NativeFile() noexcept {
const auto fd = static_cast<int>(handle_);
if (close(fd) == -1) {
throw nf7::Buffer::IOException("close failure");
// ;(
}
handle_ = std::nullopt;
}
size_t NativeFile::Read(size_t offset, uint8_t* buf, size_t size) {
if (!handle_) {
throw nf7::Buffer::IOException("not locked yet");
}
const auto fd = static_cast<int>(*handle_);
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::Buffer::IOException("lseek failure");
throw NativeFile::Exception {"lseek failure"};
}
const auto ret = read(fd, buf, size);
if (ret == -1) {
throw nf7::Buffer::IOException("read failure");
throw NativeFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::Write(size_t offset, const uint8_t* buf, size_t size) {
if (!handle_) {
throw nf7::Buffer::IOException("not locked yet");
}
const auto fd = static_cast<int>(*handle_);
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::Buffer::IOException("lseek failure");
throw nf7::NativeFile::Exception {"lseek failure"};
}
const auto ret = write(fd, buf, size);
if (ret == -1) {
throw nf7::Buffer::IOException("write failure");
throw nf7::NativeFile::Exception {"write failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::Truncate(size_t size) {
if (!handle_) {
throw nf7::Buffer::IOException("not locked yet");
}
const auto fd = static_cast<int>(*handle_);
const auto fd = static_cast<int>(handle_);
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
throw nf7::Buffer::IOException("ftruncate failure");
throw nf7::NativeFile::Exception {"ftruncate failure"};
}
return size;
}
size_t NativeFile::size() const {
if (!handle_) {
throw nf7::Buffer::IOException("not locked yet");
}
const auto fd = static_cast<int>(*handle_);
const auto ret = lseek(fd, 0, SEEK_END);
if (ret == -1) {
throw nf7::Buffer::IOException("lseek failure");
}
return static_cast<size_t>(ret);
}
void NativeFile::CleanUp() noexcept {
}
void NativeFile::Abort() noexcept {
}
size_t NativeFile::GetMemoryUsage() const noexcept {
return 0;
}
std::string NativeFile::GetDescription() const noexcept {
if (!handle_) {
return "unix file descriptor: "+path_.string();
} else {
return "unix file descriptor (active): "+path_.string();
}
}
} // namespace nf7

80
common/native_file_win.cc Normal file
View File

@@ -0,0 +1,80 @@
#include "common/native_file.hh"
extern "C" {
#include <windows.h>
}
namespace nf7 {
void NativeFile::Init() {
DWORD acc = 0;
DWORD flags = 0;
if (flags_ & kRead) {
acc |= GENERIC_READ;
flags |= OPEN_EXISTING;
}
if (flags_ & kWrite) {
acc |= GENERIC_WRITE;
flags |= OPEN_ALWAYS;
}
HANDLE h = CreateFileA(
path_.string().c_str(),
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
throw NativeFile::Exception {"open failure"};
}
handle_ = reinterpret_cast<uintptr_t>(h);
}
NativeFile::~NativeFile() noexcept {
auto h = reinterpret_cast<HANDLE>(handle_);
if (!CloseHandle(h)) {
// ;(
}
}
size_t NativeFile::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"};
}
DWORD ret;
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
throw NativeFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::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"};
}
DWORD ret;
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
throw NativeFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NativeFile::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"};
}
if (!SetEndOfFile(h)) {
throw NativeFile::Exception {"SetEndOfFile failure"};
}
return size;
}
} // namespace nf7

View File

@@ -11,7 +11,7 @@
#include "nf7.hh"
#include "common/lambda.hh"
#include "common/value.hh"
namespace nf7 {
@@ -19,52 +19,35 @@ namespace nf7 {
class Node : public File::Interface {
public:
class Editor;
class Lambda;
enum Flag : uint8_t {
kUI = 1 << 0, // UpdateNode() is called to display node
kMenu = 1 << 1,
kNone = 0,
kCustomNode = 1 << 0,
kMenu = 1 << 1,
kMenu_DirItem = 1 << 2, // use DirItem::UpdateMenu() method instead of Node's
};
using Flags = uint8_t;
Node(Flags f = 0) noexcept : flags_(f) { }
Node(Flags f) noexcept : flags_(f) { }
Node(const Node&) = default;
Node(Node&&) = default;
Node& operator=(const Node&) = default;
Node& operator=(Node&&) = default;
virtual std::shared_ptr<nf7::Lambda> CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>&) noexcept = 0;
virtual std::shared_ptr<Lambda> CreateLambda(const std::shared_ptr<Lambda>&) noexcept = 0;
virtual void UpdateNode(Editor&) noexcept { }
virtual void UpdateMenu(Editor&) noexcept { }
std::span<const std::string> input() const noexcept { return input_; }
std::span<const std::string> output() const noexcept { return output_; }
const std::string& input(size_t i) const noexcept { return input_[i]; }
const std::string& output(size_t i) const noexcept { return output_[i]; }
size_t input(std::string_view name) const {
auto itr = std::find(input_.begin(), input_.end(), name);
if (itr >= input_.end()) {
throw Exception("missing input socket: "+std::string(name));
}
return static_cast<size_t>(itr - input_.begin());
}
size_t output(std::string_view name) const {
auto itr = std::find(output_.begin(), output_.end(), name);
if (itr >= output_.end()) {
throw Exception("missing output socket: "+std::string(name));
}
return static_cast<size_t>(itr - output_.begin());
}
// The returned span is alive until next operation to the file.
virtual std::span<const std::string> GetInputs() const noexcept = 0;
virtual std::span<const std::string> GetOutputs() const noexcept = 0;
Flags flags() const noexcept { return flags_; }
protected:
Flags flags_;
std::vector<std::string> input_;
std::vector<std::string> output_;
};
class Node::Editor {
@@ -76,7 +59,8 @@ class Node::Editor {
Editor& operator=(const Editor&) = delete;
Editor& operator=(Editor&&) = delete;
virtual void Emit(Node&, size_t, nf7::Value&&) noexcept = 0;
virtual void Emit(Node&, std::string_view, nf7::Value&&) noexcept = 0;
virtual std::shared_ptr<Lambda> GetLambda(Node& node) noexcept = 0;
virtual void AddLink(Node& src_node, std::string_view src_name,
Node& dst_node, std::string_view dst_name) noexcept = 0;
@@ -87,4 +71,24 @@ class Node::Editor {
virtual std::vector<std::pair<Node*, std::string>> GetDstOf(Node&, std::string_view) const noexcept = 0;
};
class Node::Lambda : public nf7::Context {
public:
Lambda(nf7::File& f, const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
Lambda(f.env(), f.id(), parent) {
}
Lambda(nf7::Env& env, nf7::File::Id id, const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
Context(env, id, parent),
parent_(std::dynamic_pointer_cast<Node::Lambda>(parent)) {
}
virtual void Handle(
std::string_view, const nf7::Value&, const std::shared_ptr<Lambda>&) noexcept {
}
std::shared_ptr<Node::Lambda> parent() const noexcept { return parent_.lock(); }
private:
std::weak_ptr<Node::Lambda> parent_;
};
} // namespace nf7

View File

@@ -63,7 +63,7 @@ class NodeLinkStore {
links_.erase(std::remove(links_.begin(), links_.end(), lk), links_.end());
}
inline std::unique_ptr<History::Command> CreateCommandToRemoveExpired(
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
uint64_t id, std::span<const std::string> in, std::span<const std::string> out) noexcept;
std::span<const Link> items() const noexcept { return links_; }
@@ -104,9 +104,9 @@ class NodeLinkStore::SwapCommand : public History::Command {
};
std::unique_ptr<History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
uint64_t id, std::span<const std::string> in, std::span<const std::string> out) noexcept {
std::vector<std::unique_ptr<History::Command>> cmds;
std::vector<std::unique_ptr<nf7::History::Command>> cmds;
for (const auto& lk : links_) {
const bool rm =
(lk.src_id == id && std::find(out.begin(), out.end(), lk.src_name) == out.end()) ||
@@ -114,7 +114,7 @@ std::unique_ptr<History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link(lk)));
}
if (cmds.empty()) return nullptr;
return std::make_unique<AggregateCommand<History::Command>>(std::move(cmds));
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
}
} // namespace nf7

116
common/node_root_lambda.hh Normal file
View File

@@ -0,0 +1,116 @@
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include "nf7.hh"
#include "common/future.hh"
#include "common/value.hh"
namespace nf7 {
class NodeRootLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<NodeRootLambda> {
public:
struct Builder;
NodeRootLambda(const NodeRootLambda&) = delete;
NodeRootLambda(NodeRootLambda&&) = delete;
NodeRootLambda& operator=(const NodeRootLambda&) = delete;
NodeRootLambda& operator=(NodeRootLambda&&) = delete;
~NodeRootLambda() noexcept {
target_ = nullptr;
for (auto& pro : pro_) {
pro.second.Throw(std::make_exception_ptr(
nf7::Exception {"output was never satisified"}));
}
}
private:
std::shared_ptr<nf7::Node::Lambda> target_;
std::unordered_map<std::string, nf7::Future<nf7::Value>::Promise> pro_;
std::unordered_map<std::string, std::function<void(const nf7::Value&)>> handler_;
using nf7::Node::Lambda::Lambda;
void Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
const auto sname = std::string {name};
auto pitr = pro_.find(sname);
if (pitr != pro_.end()) {
pitr->second.Return(nf7::Value {v});
pro_.erase(pitr);
}
auto hitr = handler_.find(sname);
if (hitr != handler_.end()) {
hitr->second(v);
}
}
};
struct NodeRootLambda::Builder final {
public:
Builder() = delete;
Builder(nf7::File& f, nf7::Node& n,
const std::shared_ptr<nf7::Context>& ctx = nullptr) noexcept :
prod_(new NodeRootLambda {f, ctx}), target_(n.CreateLambda(prod_)), node_(&n) {
prod_->target_ = target_;
}
void CheckOutput(std::string_view name) const {
auto out = node_->GetOutputs();
if (out.end() == std::find(out.begin(), out.end(), name)) {
throw nf7::Exception {"required output is missing: "+std::string {name}};
}
}
void CheckInput(std::string_view name) const {
auto in = node_->GetInputs();
if (in.end() == std::find(in.begin(), in.end(), name)) {
throw nf7::Exception {"required input is missing: "+std::string {name}};
}
}
nf7::Future<nf7::Value> Receive(const std::string& name) {
assert(!built_);
CheckOutput(name);
auto [itr, added] =
prod_->pro_.try_emplace(name, nf7::Future<nf7::Value>::Promise {});
assert(added);
return itr->second.future();
}
void Listen(const std::string& name, std::function<void(const nf7::Value&)>&& f) {
assert(!built_);
CheckOutput(name);
prod_->handler_[name] = std::move(f);
}
std::shared_ptr<NodeRootLambda> Build() noexcept {
assert(!built_);
built_ = true;
return prod_;
}
void Send(std::string_view name, const nf7::Value& v) {
assert(built_);
CheckInput(name);
target_->Handle(name, v, prod_);
}
private:
bool built_ = false;
std::shared_ptr<NodeRootLambda> prod_;
std::shared_ptr<nf7::Node::Lambda> target_;
nf7::Node* const node_;
};
} // namespace nf7

View File

@@ -1,5 +1,6 @@
#pragma once
#include <atomic>
#include <deque>
#include <functional>
#include <mutex>
@@ -20,10 +21,12 @@ class Queue {
void Push(T&& task) noexcept {
std::unique_lock<std::mutex> _(mtx_);
++n_;
tasks_.push_back(std::move(task));
}
void Interrupt(T&& task) noexcept {
std::unique_lock<std::mutex> _(mtx_);
++n_;
tasks_.push_front(std::move(task));
}
std::optional<T> Pop() noexcept {
@@ -31,24 +34,19 @@ class Queue {
if (tasks_.empty()) return std::nullopt;
auto ret = std::move(tasks_.front());
tasks_.pop_front();
--n_;
k.unlock();
return ret;
}
void Clear() noexcept {
std::unique_lock<std::mutex> k(mtx_);
tasks_.clear();
}
bool size() const noexcept {
std::unique_lock<std::mutex> k(const_cast<std::mutex&>(mtx_));
return tasks_.size();
}
size_t size() const noexcept { return n_; }
protected:
std::mutex mtx_;
private:
std::atomic<size_t> n_;
std::deque<T> tasks_;
};

114
common/sequencer.hh Normal file
View File

@@ -0,0 +1,114 @@
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>
#include "nf7.hh"
#include "common/value.hh"
namespace nf7 {
class Sequencer : public nf7::File::Interface {
public:
class Editor;
class Session;
class Lambda;
enum Flag : uint8_t {
kNone = 0,
kCustomItem = 1 << 0, // uses UpdateItem() to draw an item on timeline if enable
kParamPanel = 1 << 1,
kTooltip = 1 << 2,
kMenu = 1 << 3,
};
using Flags = uint8_t;
Sequencer() = delete;
Sequencer(Flags flags) noexcept : flags_(flags) { }
Sequencer(const Sequencer&) = delete;
Sequencer(Sequencer&&) = delete;
Sequencer& operator=(const Sequencer&) = delete;
Sequencer& operator=(Sequencer&&) = delete;
// Sequencer* is a dummy parameter to avoid issues of multi inheritance.
virtual std::shared_ptr<Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept = 0;
virtual void UpdateItem(Editor&) noexcept { }
virtual void UpdateParamPanel(Editor&) noexcept { }
virtual void UpdateTooltip(Editor&) noexcept { }
virtual void UpdateMenu(Editor&) noexcept { }
Flags flags() const noexcept { return flags_; }
private:
Flags flags_;
};
class Sequencer::Editor {
public:
Editor() noexcept = default;
virtual ~Editor() noexcept = default;
Editor(const Editor&) = delete;
Editor(Editor&&) = delete;
Editor& operator=(const Editor&) = delete;
Editor& operator=(Editor&&) = delete;
};
class Sequencer::Session {
public:
class UnknownNameException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
Session() = default;
virtual ~Session() = default;
Session(const Session&) = delete;
Session(Session&&) = delete;
Session& operator=(const Session&) = delete;
Session& operator=(Session&&) = delete;
virtual const nf7::Value* Peek(std::string_view) noexcept = 0;
virtual std::optional<nf7::Value> Receive(std::string_view) noexcept = 0;
const nf7::Value& PeekOrThrow(std::string_view name) {
if (auto v = Peek(name)) {
return *v;
}
throw UnknownNameException {std::string {name}+" is unknown"};
}
nf7::Value ReceiveOrThrow(std::string_view name) {
if (auto v = Receive(name)) {
return std::move(*v);
}
throw UnknownNameException {std::string {name}+" is unknown"};
}
virtual void Send(std::string_view, nf7::Value&&) noexcept = 0;
// thread-safe
virtual void Finish() noexcept = 0;
};
class Sequencer::Lambda : public nf7::Context {
public:
Lambda(nf7::File& f, const std::shared_ptr<Context>& ctx = nullptr) noexcept :
Lambda(f.env(), f.id(), ctx) {
}
Lambda(nf7::Env& env, nf7::File::Id id,
const std::shared_ptr<nf7::Context>& ctx = nullptr) noexcept :
Context(env, id, ctx) {
}
virtual void Run(const std::shared_ptr<Sequencer::Session>&) noexcept = 0;
};
} // namespace nf7

View File

@@ -0,0 +1,53 @@
#pragma once
#include <cassert>
#include <memory>
#include <utility>
#include <vector>
#include "common/aggregate_command.hh"
#include "common/generic_history.hh"
namespace nf7 {
class SquashedHistory : public nf7::GenericHistory {
public:
SquashedHistory() = default;
SquashedHistory(const SquashedHistory&) = delete;
SquashedHistory(SquashedHistory&&) = default;
SquashedHistory& operator=(const SquashedHistory&) = delete;
SquashedHistory& operator=(SquashedHistory&&) = default;
Command& Add(std::unique_ptr<Command>&& cmd) noexcept override {
staged_.push_back(std::move(cmd));
return *staged_.back();
}
bool Squash() noexcept {
if (staged_.size() == 0) {
return false;
}
nf7::GenericHistory::Add(
std::make_unique<nf7::AggregateCommand>(std::move(staged_)));
return true;
}
void Clear() noexcept {
nf7::GenericHistory::Clear();
staged_.clear();
}
void UnDo() override {
assert(staged_.size() == 0);
GenericHistory::UnDo();
}
void ReDo() override {
assert(staged_.size() == 0);
GenericHistory::ReDo();
}
private:
std::vector<std::unique_ptr<Command>> staged_;
};
} // namespace nf7

View File

@@ -1,77 +0,0 @@
#pragma once
#include <memory>
#include <optional>
#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 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();
}
auto fu() noexcept { return *fu_; }
protected:
virtual typename nf7::Future<T>::Coro Proc() noexcept = 0;
private:
std::optional<typename nf7::Future<T>::Coro> coro_;
std::optional<nf7::Future<T>> fu_;
};
template <typename T>
class Task<T>::Holder final {
public:
Holder() = default;
Holder(const std::shared_ptr<Task<T>>& ctx) noexcept : ctx_(ctx) {
}
~Holder() noexcept {
Abort();
}
Holder(const Holder&) = delete;
Holder(Holder&& src) noexcept = default;
Holder& operator=(const Holder&) = delete;
Holder& operator=(Holder&& src) noexcept {
if (this != &src) {
Abort();
ctx_ = std::move(src.ctx_);
}
return *this;
}
void Abort() noexcept {
if (auto ctx = ctx_.lock()) ctx->Abort();
ctx_ = {};
}
std::shared_ptr<Task<T>> lock() const noexcept { return ctx_.lock(); }
private:
std::weak_ptr<Task<T>> ctx_;
};
} // namespace nf7

View File

@@ -8,18 +8,22 @@
#include "nf7.hh"
#include "common/queue.hh"
#include "common/timed_queue.hh"
namespace nf7 {
// a thread emulation using nf7::Env::ExecAsync
template <typename Runner, typename Task>
class Thread final : public std::enable_shared_from_this<Thread<Runner, Task>> {
class Thread final : public nf7::Context,
public std::enable_shared_from_this<Thread<Runner, Task>> {
public:
Thread() = delete;
Thread(nf7::Env& env, Runner&& runner) noexcept :
env_(&env), runner_(std::move(runner)) {
Thread(nf7::File& f, Runner&& runner) noexcept :
Thread(f.env(), f.id(), std::move(runner)) {
}
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;
@@ -27,8 +31,8 @@ class Thread final : public std::enable_shared_from_this<Thread<Runner, Task>> {
Thread& operator=(const Thread&) = delete;
Thread& operator=(Thread&&) = delete;
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& t) noexcept {
q_.Push({ctx, std::move(t)});
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& t, nf7::Env::Time time = {}) noexcept {
q_.Push(time, {ctx, std::move(t)});
HandleNext(true /* = first */);
}
@@ -40,7 +44,7 @@ class Thread final : public std::enable_shared_from_this<Thread<Runner, Task>> {
Env* const env_;
Runner runner_;
nf7::Queue<Pair> q_;
nf7::TimedQueue<Pair> q_;
std::mutex mtx_;
bool working_ = false;
@@ -48,19 +52,24 @@ class Thread final : public std::enable_shared_from_this<Thread<Runner, Task>> {
std::atomic<size_t> tasks_done_ = 0;
using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this;
void HandleNext(bool first = false) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (std::exchange(working_, true) && first) return;
auto self = shared_from_this();
if (auto p = q_.Pop()) {
k.unlock();
auto self = std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this();
env_->ExecAsync(p->first, [this, self, t = std::move(p->second)]() mutable {
runner_(std::move(t));
++tasks_done_;
HandleNext();
});
} else if (auto time = q_.next()) {
working_ = false;
env_->ExecAsync(
shared_from_this(), [this, self]() mutable { HandleNext(); }, *time);
} else {
working_ = false;
}

111
common/timed_queue.hh Normal file
View File

@@ -0,0 +1,111 @@
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>
#include <vector>
#include "nf7.hh"
namespace nf7 {
template <typename T>
class TimedQueue {
public:
TimedQueue() = default;
TimedQueue(const TimedQueue&) = delete;
TimedQueue(TimedQueue&&) = delete;
TimedQueue& operator=(const TimedQueue&) = delete;
TimedQueue& operator=(TimedQueue&&) = delete;
void Push(nf7::Env::Time time, T&& task) noexcept {
std::unique_lock<std::mutex> k(mtx_);
++n_;
q_.push(Item {.time = time, .index = index_++, .task = std::move(task)});
}
std::optional<T> Pop(nf7::Env::Time now = nf7::Env::Clock::now()) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (q_.empty() || q_.top().time > now) {
return std::nullopt;
}
auto ret = std::move(q_.top());
q_.pop();
--n_;
k.unlock();
return ret.task;
}
std::optional<nf7::Env::Time> next() const noexcept {
std::unique_lock<std::mutex> k(mtx_);
return next_();
}
size_t size() const noexcept { return n_; }
protected:
mutable std::mutex mtx_;
std::optional<nf7::Env::Time> next_() const noexcept {
if (q_.empty()) return std::nullopt;
return q_.top().time;
}
private:
struct Item final {
nf7::Env::Time time;
size_t index;
T task;
};
struct Comp final {
bool operator()(const Item& a, const Item& b) noexcept {
return a.time != b.time? a.time > b.time: a.index > b.index;
}
};
std::atomic<size_t> n_;
size_t index_ = 0;
std::priority_queue<Item, std::vector<Item>, Comp> q_;
};
template <typename T>
class TimedWaitQueue final : private TimedQueue<T> {
public:
TimedWaitQueue() = default;
TimedWaitQueue(const TimedWaitQueue&) = delete;
TimedWaitQueue(TimedWaitQueue&&) = delete;
TimedWaitQueue& operator=(const TimedWaitQueue&) = delete;
TimedWaitQueue& operator=(TimedWaitQueue&&) = delete;
void Push(nf7::Env::Time time, T&& task) noexcept {
TimedQueue<T>::Push(time, std::move(task));
cv_.notify_all();
}
using TimedQueue<T>::Pop;
void Notify() noexcept {
cv_.notify_all();
}
void Wait() noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (auto t = next_()) {
cv_.wait_until(k, *t);
} else {
cv_.wait(k);
}
}
using TimedQueue<T>::next;
using TimedQueue<T>::size;
private:
using TimedQueue<T>::mtx_;
using TimedQueue<T>::next_;
std::condition_variable cv_;
};
} // namespace nf7

73
common/util_string.hh Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <algorithm>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace nf7::util {
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()) {
auto j = str.find(c, i);
if (j == std::string::npos) j = str.size();
ret = str.substr(i, j-i);
i = j+1;
}
if (ret.empty()) return std::nullopt;
return ret;
}
inline void SplitAndAppend(std::vector<std::string>& dst, std::string_view src, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(src, c, itr)) {
dst.emplace_back(*term);
}
}
inline void JoinAndAppend(std::string& dst, std::span<const std::string> src, char c = '\n') noexcept {
for (auto& str : src) {
dst += str;
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,
const std::function<bool(std::string_view)> validator, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(v, c, itr)) {
if (validator(*term)) {
return term;
}
}
return std::nullopt;
}
inline std::optional<std::string_view> SplitAndValidate(
std::string_view v,
const std::function<void(std::string_view)> validator, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(v, c, itr)) {
try {
validator(*term);
} catch (nf7::Exception&) {
return term;
}
}
return std::nullopt;
}
} // namespace nf7::util

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
@@ -69,6 +70,8 @@ class Value {
Value& operator=(const Vector& v) noexcept { value_ = v; return *this; }
Value(Vector&& v) noexcept { value_ = std::move(v); }
Value& operator=(Vector&& v) noexcept { value_ = std::move(v); return *this; }
Value(const ConstVector& v) noexcept { value_ = std::const_pointer_cast<std::vector<uint8_t>>(v); }
Value& operator=(const ConstVector& v) noexcept { value_ = std::const_pointer_cast<std::vector<uint8_t>>(v); return *this; }
Value(std::vector<uint8_t>&& v) noexcept { value_ = std::make_shared<std::vector<uint8_t>>(std::move(v)); }
Value& operator=(std::vector<uint8_t>&& v) noexcept { value_ = std::make_shared<std::vector<uint8_t>>(std::move(v)); return *this; }
Value(const Tuple& v) noexcept : value_(v) { }
@@ -118,6 +121,20 @@ class Value {
const ConstTuple tuple() const { return get<Tuple>(); }
const DataPtr& data() const { return get<DataPtr>(); }
template <typename I>
I integer() const {
const auto ret = integer();
if constexpr (std::is_unsigned<I>::value) {
if (ret < 0) {
throw IncompatibleException("integer underflow");
}
} else {
if (ret != static_cast<Integer>(static_cast<I>(ret))) {
throw IncompatibleException("integer out of range");
}
}
return static_cast<I>(ret);
}
const Value& tuple(size_t idx) const {
auto& tup = *tuple();
return idx < tup.size()? tup[idx].second:

View File

@@ -1,50 +0,0 @@
#include <condition_variable>
#include <deque>
#include <mutex>
#include "common/queue.hh"
namespace nf7 {
// Queue<T> with Wait() method
template <typename T>
class WaitQueue : private Queue<T> {
public:
WaitQueue() = default;
WaitQueue(const WaitQueue&) = default;
WaitQueue(WaitQueue&&) = default;
WaitQueue& operator=(const WaitQueue&) = default;
WaitQueue& operator=(WaitQueue&&) = default;
void Push(T&& task) noexcept {
Queue<T>::Push(std::move(task));
cv_.notify_all();
}
using Queue<T>::Pop;
void Notify() noexcept {
cv_.notify_all();
}
void Wait() noexcept {
std::unique_lock<std::mutex> k(mtx_);
cv_.wait(k);
}
void WaitFor(auto dur) noexcept {
std::unique_lock<std::mutex> k(mtx_);
cv_.wait_for(k, dur);
}
void WaitUntil(auto time) noexcept {
std::unique_lock<std::mutex> k(mtx_);
cv_.wait_until(k, time);
}
using Queue<T>::size;
private:
using Queue<T>::mtx_;
std::condition_variable cv_;
};
} // namespace nf7

View File

@@ -20,7 +20,9 @@ struct serializer<
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::unique_ptr<nf7::File>& f) {
ar(std::string(f->type().name()));
ar(std::string {f->type().name()});
typename Archive::ChunkGuard guard {ar};
f->Serialize(ar);
return ar;
}
@@ -28,7 +30,39 @@ struct serializer<
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
std::string name;
ar(name);
f = nf7::File::registry(name).Deserialize(nf7::Env::Peek(), ar);
auto& type = nf7::File::registry(name);
try {
typename Archive::ChunkGuard guard {ar};
f = type.Deserialize(ar);
guard.ValidateEnd();
} catch (...) {
f = nullptr;
throw;
}
return ar;
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
std::shared_ptr<nf7::File>> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf(f.get());
ar(uf);
uf.release();
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf;
ar(uf);
f = std::move(uf);
return ar;
}
};

25
common/yas_std_variant.hh Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <yas/serialize.hpp>
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
std::monostate> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::monostate&) {
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, std::monostate&) {
return ar;
}
};
} // namespace yas::detail

View File

@@ -19,17 +19,26 @@ namespace {
class AudioContext final : public nf7::File, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<AudioContext> kType = {"Audio/Context", {"DirItem",}};
static inline const nf7::GenericTypeInfo<AudioContext> kType = {
"Audio/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives miniaudio context.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::audio::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"there's no merit to use multiple contexts");
ImGui::Bullet(); ImGui::TextUnformatted(
"the context remains alive after file deletion until unused");
}
class Queue;
AudioContext(Env&) noexcept;
AudioContext(nf7::Env&) noexcept;
AudioContext(Env& env, Deserializer&) : AudioContext(env) {
AudioContext(nf7::Deserializer& ar) noexcept : AudioContext(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<AudioContext>(env);
}
@@ -37,7 +46,7 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
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::audio::Queue>(t).Select(this, q_.get());
}
@@ -84,7 +93,8 @@ class AudioContext::Queue final : public nf7::audio::Queue,
};
Queue() = delete;
Queue(Env& env) : env_(&env), th_(std::make_shared<Thread>(env, Runner {*this})) {
Queue(AudioContext& f) noexcept :
env_(&f.env()), th_(std::make_shared<Thread>(f, Runner {*this})) {
}
~Queue() noexcept {
th_->Push(
@@ -97,9 +107,9 @@ class AudioContext::Queue final : public nf7::audio::Queue,
Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete;
void Init(Env& env) noexcept {
void Init() noexcept {
th_->Push(
std::make_shared<nf7::GenericContext>(env, 0, "creating ma_context"),
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())) {
@@ -128,8 +138,8 @@ class AudioContext::Queue final : public nf7::audio::Queue,
};
AudioContext::AudioContext(Env& env) noexcept :
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
q_(std::make_shared<Queue>(env)) {
q_->Init(env);
q_(std::make_shared<Queue>(*this)) {
q_->Init();
}

View File

@@ -6,6 +6,7 @@
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <variant>
@@ -20,9 +21,10 @@
#include "common/audio_queue.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/lambda.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
@@ -34,63 +36,75 @@ using namespace std::literals;
namespace nf7 {
namespace {
class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<IO> kType = {"Audio/IO", {"DirItem",}};
static inline const GenericTypeInfo<Device> kType = {
"Audio/Device", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Manages ring buffer and sends PCM samples to actual device.");
ImGui::Bullet();
ImGui::TextUnformatted("requires nf7::audio::Queue with name '_audio' on upper dirs");
}
class Ring;
class PlaybackLambda;
class CaptureLambda;
using DeviceSelector = std::variant<size_t, std::string>;
struct DeviceSelectorVisitor;
using Selector = std::variant<size_t, std::string>;
struct SelectorVisitor;
static ma_device_config defaultConfig() noexcept {
ma_device_config cfg;
cfg = ma_device_config_init(ma_device_type_playback);
cfg.sampleRate = 480000;
cfg.sampleRate = 48000;
cfg.playback.format = ma_format_f32;
cfg.playback.channels = 2;
cfg.capture.format = ma_format_f32;
cfg.capture.channels = 2;
return cfg;
}
IO(Env& env,
DeviceSelector&& sel = size_t{0},
const ma_device_config& cfg = defaultConfig()) noexcept :
File(kType, env), nf7::DirItem(DirItem::kMenu | DirItem::kTooltip),
data_(std::make_shared<Data>()),
selector_(std::move(sel)), cfg_(cfg) {
Device(nf7::Env& env, Selector&& sel = size_t{0}, const ma_device_config& cfg = defaultConfig()) noexcept :
nf7::FileBase(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone),
data_(std::make_shared<AsyncData>(*this)),
selector_(std::move(sel)), cfg_(cfg),
config_popup_(std::make_shared<ConfigPopup>()) {
nf7::FileBase::Install(data_->log);
}
IO(Env& env, Deserializer& ar) : IO(env) {
Device(nf7::Deserializer& ar) : Device(ar.env()) {
ar(selector_, cfg_);
}
void Serialize(Serializer& ar) const noexcept override {
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(selector_, cfg_);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<IO>(env, DeviceSelector {selector_}, cfg_);
return std::make_unique<Device>(env, Selector {selector_}, cfg_);
}
std::shared_ptr<nf7::Lambda> CreateLambda(const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
std::shared_ptr<Lambda> CreateLambda(const std::shared_ptr<Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override;
std::span<const std::string> GetOutputs() const noexcept override;
void Handle(const Event&) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateNode(Node::Editor&) noexcept override { }
static bool UpdateModeSelector(ma_device_type*) noexcept;
static const ma_device_info* UpdateSelector(Selector*, ma_device_info*, size_t) noexcept;
static void UpdatePresetSelector(ma_device_config*, const ma_device_info*) noexcept;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
const char* popup_ = nullptr;
struct Data {
struct AsyncData {
public:
Data() noexcept : ring(std::make_unique<Ring>()) {
AsyncData(nf7::File& f) noexcept :
log(f), ring(std::make_unique<Ring>()) {
}
nf7::LoggerRef log;
@@ -101,17 +115,21 @@ class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
std::optional<ma_device> dev;
std::atomic<size_t> busy = 0;
};
std::shared_ptr<Data> data_;
std::shared_ptr<AsyncData> data_;
// persistent params
DeviceSelector selector_;
Selector selector_;
ma_device_config cfg_;
// ConfigPopup param
// ConfigPopup param (must be shared_ptr because saves fetched device list async)
struct ConfigPopup final : std::enable_shared_from_this<ConfigPopup> {
ma_device_config cfg;
DeviceSelector selector;
bool open = false;
// params
ma_device_config cfg;
Selector selector;
// fetched devices
std::atomic<bool> fetching = false;
ma_device_info* devs = nullptr;
ma_uint32 devs_n = 0;
@@ -124,7 +142,7 @@ class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
std::make_shared<nf7::GenericContext>(f, "fetching device list"),
[this, self = shared_from_this(), mode](auto ma) {
try {
auto [ptr, n] = IO::FetchDevs(ma, mode);
auto [ptr, n] = Device::FetchDevs(ma, mode);
devs = ptr;
devs_n = static_cast<ma_uint32>(n);
} catch (nf7::Exception&) {
@@ -140,8 +158,6 @@ class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
void InitDev() noexcept;
void DeinitDev() noexcept;
void BuildNode() noexcept;
static std::pair<ma_device_info*, size_t> FetchDevs(ma_context* ctx, ma_device_type mode) {
ma_device_info* devs = nullptr;
@@ -186,11 +202,6 @@ class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
}
static bool UpdateModeSelector(ma_device_type*) noexcept;
static const ma_device_info* UpdateDeviceSelector(DeviceSelector*, ma_device_info*, size_t) noexcept;
static void UpdatePresetSelector(ma_device_config*, const ma_device_info*) noexcept;
nf7::Value infoTuple() const noexcept {
return nf7::Value {std::vector<nf7::Value::TuplePair> {
{"sampleRate", {static_cast<nf7::Value::Integer>(cfg_.sampleRate)}},
@@ -199,7 +210,7 @@ class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
}
};
class IO::Ring final {
class Device::Ring final {
public:
static constexpr auto kDur = 3000; /* msecs */
@@ -220,18 +231,26 @@ class IO::Ring final {
}
// for playback mode: mix samples into this ring
uint64_t Mix(const float* ptr, size_t n, uint64_t time) noexcept {
std::pair<uint64_t, uint64_t> Mix(const float* ptr, size_t n, uint64_t time) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (time < time_) time = time_;
if (time-time_ > buf_.size()) return time_+buf_.size();
if (time < time_) {
time = time_;
}
if (time-time_ > buf_.size()) {
return {time_+buf_.size(), 0};
}
n = std::min(n, buf_.size());
if (time+n-time_ > buf_.size()) {
n = buf_.size() - (time-time_);
}
const size_t vn = std::min(n, buf_.size());
const size_t offset = (time-time_begin_)%buf_.size();
for (size_t srci = 0, dsti = offset; srci < vn; ++srci, ++dsti) {
for (size_t srci = 0, dsti = offset; srci < n; ++srci, ++dsti) {
if (dsti >= buf_.size()) dsti = 0;
buf_[dsti] += ptr[srci];
}
return time+vn;
return {time+n, n};
}
// for playback mode: consume samples in this ring
void Consume(float* dst, size_t n) noexcept {
@@ -282,124 +301,101 @@ class IO::Ring final {
std::atomic<uint64_t> time_ = 0;
};
class IO::PlaybackLambda final : public nf7::Lambda,
public std::enable_shared_from_this<IO::PlaybackLambda> {
class Device::PlaybackLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Device::PlaybackLambda> {
public:
static inline const std::vector<std::string> kInputs = {"get_info", "mix"};
static inline const std::vector<std::string> kOutputs = {"info", "mixed_size"};
enum {
kInGetInfo = 0,
kInSamples = 1,
kOutInfo = 0,
kOutSampleCount = 1,
};
PlaybackLambda() = delete;
PlaybackLambda(IO& f, const std::shared_ptr<Owner>& owner) noexcept :
Lambda(owner), data_(f.data_), info_(f.infoTuple()) {
PlaybackLambda(Device& f, const std::shared_ptr<Lambda>& parent) noexcept :
Lambda(f, parent), data_(f.data_), info_(f.infoTuple()) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>& caller) noexcept override
try {
switch (idx) {
case kInGetInfo:
caller->Handle(kOutInfo, nf7::Value {info_}, shared_from_this());
break;
case kInSamples: {
void Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<Lambda>& caller) noexcept override {
if (name == "get_info") {
caller->Handle("info", nf7::Value {info_}, shared_from_this());
return;
}
if (name == "mix") {
const auto& vec = v.vector();
const auto ptr = reinterpret_cast<const float*>(vec->data());
const auto n = vec->size()/sizeof(float);
auto ptime = time_;
time_ = data_->ring->Mix(ptr, n, time_);
if (time_ < ptime) ptime = time_;
const auto [time, mixed] = data_->ring->Mix(ptr, n, time_);
time_ = time;
caller->Handle(kOutSampleCount, static_cast<nf7::Value::Integer>(time_-ptime), shared_from_this());
} break;
default:
throw nf7::Exception("got unknown input");
const auto ret = static_cast<nf7::Value::Integer>(mixed);
caller->Handle("mixed_size", ret, shared_from_this());
return;
}
} catch (nf7::Exception& e) {
data_->log.Warn(e.msg());
data_->log.Warn("got unknown input");
}
private:
std::shared_ptr<Data> data_;
std::shared_ptr<AsyncData> data_;
nf7::Value info_;
uint64_t time_ = 0;
};
class IO::CaptureLambda final : public nf7::Lambda,
std::enable_shared_from_this<IO::CaptureLambda> {
class Device::CaptureLambda final : public nf7::Node::Lambda,
std::enable_shared_from_this<Device::CaptureLambda> {
public:
static inline const std::vector<std::string> kInputs = {"get_info", "peek"};
static inline const std::vector<std::string> kOutputs = {"info", "samples"};
enum {
kInGetInfo = 0,
kInPeek = 1,
kOutInfo = 0,
kOutSamples = 1,
};
CaptureLambda() = delete;
CaptureLambda(IO& f, const std::shared_ptr<Owner>& owner) noexcept :
Lambda(owner), data_(f.data_), info_(f.infoTuple()) {
CaptureLambda(Device& f, const std::shared_ptr<Lambda>& parent) noexcept :
Lambda(f, parent), data_(f.data_), info_(f.infoTuple()) {
}
void Handle(size_t idx, nf7::Value&&, const std::shared_ptr<nf7::Lambda>& caller) noexcept override
try {
switch (idx) {
case kInGetInfo:
caller->Handle(kOutInfo, nf7::Value {info_}, shared_from_this());
break;
case kInPeek: {
void Handle(std::string_view name, const nf7::Value&,
const std::shared_ptr<Lambda>& caller) noexcept override {
if (name == "get_info") {
caller->Handle("info", nf7::Value {info_}, shared_from_this());
return;
}
if (name == "peek") {
std::vector<uint8_t> samples;
if (time_) {
time_ = data_->ring->Peek(samples, *time_);
} else {
time_ = data_->ring->time();
}
caller->Handle(kOutSamples, {std::move(samples)}, shared_from_this());
} break;
default:
throw nf7::Exception("got unknown input");
caller->Handle("samples", {std::move(samples)}, shared_from_this());
return;
}
} catch (nf7::Exception& e) {
data_->log.Warn(e.msg());
data_->log.Warn("got unknown input");
}
private:
std::shared_ptr<Data> data_;
std::shared_ptr<AsyncData> data_;
nf7::Value info_;
std::optional<uint64_t> time_;
};
std::shared_ptr<nf7::Lambda> IO::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept {
std::shared_ptr<Node::Lambda> Device::CreateLambda(const std::shared_ptr<Lambda>& parent) noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
return std::make_shared<IO::PlaybackLambda>(*this, owner);
return std::make_shared<Device::PlaybackLambda>(*this, parent);
case ma_device_type_capture:
return std::make_shared<IO::CaptureLambda>(*this, owner);
return std::make_shared<Device::CaptureLambda>(*this, parent);
default:
std::abort();
}
}
struct IO::DeviceSelectorVisitor final {
struct Device::SelectorVisitor final {
public:
DeviceSelectorVisitor() = delete;
DeviceSelectorVisitor(ma_device_info* info, size_t n) noexcept : info_(info), n_(n) {
SelectorVisitor() = delete;
SelectorVisitor(ma_device_info* info, size_t n) noexcept : info_(info), n_(n) {
}
DeviceSelectorVisitor(const DeviceSelectorVisitor&) = delete;
DeviceSelectorVisitor(DeviceSelectorVisitor&&) = delete;
DeviceSelectorVisitor& operator=(const DeviceSelectorVisitor&) = delete;
DeviceSelectorVisitor& operator=(DeviceSelectorVisitor&&) = delete;
SelectorVisitor(const SelectorVisitor&) = delete;
SelectorVisitor(SelectorVisitor&&) = delete;
SelectorVisitor& operator=(const SelectorVisitor&) = delete;
SelectorVisitor& operator=(SelectorVisitor&&) = delete;
ma_device_info* operator()(const size_t& idx) noexcept {
return idx < n_? &info_[idx]: nullptr;
@@ -418,7 +414,30 @@ struct IO::DeviceSelectorVisitor final {
};
void IO::InitDev() noexcept {
std::span<const std::string> Device::GetInputs() const noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
return PlaybackLambda::kInputs;
case ma_device_type_capture:
return CaptureLambda::kInputs;
default:
assert(false);
}
return {};
}
std::span<const std::string> Device::GetOutputs() const noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
return PlaybackLambda::kOutputs;
case ma_device_type_capture:
return CaptureLambda::kOutputs;
default:
assert(false);
}
return {};
}
void Device::InitDev() noexcept {
if (!data_->aq) {
data_->log.Error("audio queue is missing");
return;
@@ -446,7 +465,7 @@ void IO::InitDev() noexcept {
}
auto [devs, devs_n] = FetchDevs(ma, cfg.deviceType);
auto dinfo = std::visit(DeviceSelectorVisitor {devs, devs_n}, sel);
auto dinfo = std::visit(SelectorVisitor {devs, devs_n}, sel);
if (!dinfo) {
throw nf7::Exception("missing device");
}
@@ -475,7 +494,7 @@ void IO::InitDev() noexcept {
--d->busy;
});
}
void IO::DeinitDev() noexcept {
void Device::DeinitDev() noexcept {
if (!data_->aq) {
data_->log.Error("audio queue is missing");
return;
@@ -491,33 +510,18 @@ void IO::DeinitDev() noexcept {
--d->busy;
});
}
void IO::BuildNode() noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
nf7::Node::input_ = PlaybackLambda::kInputs;
nf7::Node::output_ = PlaybackLambda::kOutputs;
break;
case ma_device_type_capture:
nf7::Node::input_ = CaptureLambda::kInputs;
nf7::Node::output_ = CaptureLambda::kOutputs;
break;
default:
assert(false);
}
nf7::File::Touch();
}
void IO::Handle(const Event& ev) noexcept {
void Device::Handle(const Event& ev) noexcept {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case Event::kAdd:
data_->log.SetUp(*this);
try {
data_->aq =
ResolveUpwardOrThrow("_audio").
interfaceOrThrow<nf7::audio::Queue>().self();
InitDev();
BuildNode();
} catch (nf7::Exception&) {
data_->log.Info("audio context is not found");
}
@@ -528,7 +532,6 @@ void IO::Handle(const Event& ev) noexcept {
DeinitDev();
}
data_->aq = nullptr;
data_->log.TearDown();
return;
default:
@@ -536,20 +539,17 @@ void IO::Handle(const Event& ev) noexcept {
}
}
void Device::Update() noexcept {
nf7::FileBase::Update();
void IO::Update() noexcept {
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
if (std::exchange(config_popup_->open, false)) {
ImGui::OpenPopup("ConfigPopup");
}
if (ImGui::BeginPopup("ConfigPopup")) {
auto& p = config_popup_;
ImGui::TextUnformatted("Audio/Output");
if (ImGui::IsWindowAppearing()) {
if (!p) {
p = std::make_shared<ConfigPopup>();
}
p->cfg = cfg_;
p->selector = selector_;
@@ -565,7 +565,7 @@ void IO::Update() noexcept {
}
const ma_device_info* dev = nullptr;
if (!p->fetching) {
dev = UpdateDeviceSelector(&p->selector, p->devs, p->devs_n);
dev = UpdateSelector(&p->selector, p->devs, p->devs_n);
} else {
ImGui::LabelText("device", "fetching list...");
}
@@ -583,12 +583,12 @@ void IO::Update() noexcept {
cfg_ = p->cfg;
selector_ = p->selector;
InitDev();
BuildNode();
Touch();
}
ImGui::EndPopup();
}
}
void IO::UpdateMenu() noexcept {
void Device::UpdateMenu() noexcept {
if (cfg_.deviceType == ma_device_type_playback) {
if (ImGui::MenuItem("simulate sinwave output for 1 sec")) {
const auto wave = GenerateSineWave(cfg_.sampleRate, cfg_.playback.channels);
@@ -597,10 +597,10 @@ void IO::UpdateMenu() noexcept {
ImGui::Separator();
}
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
config_popup_->open = true;
}
}
void IO::UpdateTooltip() noexcept {
void Device::UpdateTooltip() noexcept {
const char* mode =
cfg_.deviceType == ma_device_type_playback? "playback":
cfg_.deviceType == ma_device_type_capture ? "capture":
@@ -611,7 +611,7 @@ void IO::UpdateTooltip() noexcept {
ImGui::Text("channels : %" PRIu32, cfg_.playback.channels);
ImGui::Text("sample rate: %" PRIu32, cfg_.sampleRate);
}
bool IO::UpdateModeSelector(ma_device_type* m) noexcept {
bool Device::UpdateModeSelector(ma_device_type* m) noexcept {
const char* mode =
*m == ma_device_type_playback? "playback":
*m == ma_device_type_capture? "capture":
@@ -630,9 +630,9 @@ bool IO::UpdateModeSelector(ma_device_type* m) noexcept {
}
return ret;
}
const ma_device_info* IO::UpdateDeviceSelector(
DeviceSelector* sel, ma_device_info* devs, size_t n) noexcept {
const auto dev = std::visit(DeviceSelectorVisitor {devs, n}, *sel);
const ma_device_info* Device::UpdateSelector(
Selector* sel, ma_device_info* devs, size_t n) noexcept {
const auto dev = std::visit(SelectorVisitor {devs, n}, *sel);
if (ImGui::BeginCombo("device", dev? dev->name: "(missing)")) {
for (size_t i = 0; i < n; ++i) {
@@ -665,7 +665,7 @@ const ma_device_info* IO::UpdateDeviceSelector(
}
return dev;
}
void IO::UpdatePresetSelector(ma_device_config* cfg, const ma_device_info* dev) noexcept {
void Device::UpdatePresetSelector(ma_device_config* cfg, const ma_device_info* dev) noexcept {
auto& srate = cfg->sampleRate;
auto& ch = GetChannels(*cfg);

View File

@@ -17,23 +17,28 @@
namespace nf7 {
namespace {
class LuaContext final : public nf7::File,
public nf7::DirItem {
class LuaContext final : public nf7::File, public nf7::DirItem {
public:
static inline const GenericTypeInfo<LuaContext> kType = {"LuaJIT/Context", {"DirItem",}};
static inline const GenericTypeInfo<LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::luajit::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"create multiple contexts to execute LuaJIT paralelly");
ImGui::Bullet(); ImGui::TextUnformatted(
"the thread remains alive after file deletion until unused");
}
class Queue;
LuaContext(Env& env) noexcept :
LuaContext(nf7::Env& env) :
File(kType, env), DirItem(DirItem::kTooltip) {
try {
q_ = std::make_shared<Queue>(env);
} catch (nf7::Exception&) {
// Thread construction failure (ignore it)
}
q_ = std::make_shared<Queue>(*this);
}
LuaContext(Env& env, Deserializer&) noexcept : LuaContext(env) {
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
}
void Serialize(Serializer&) const noexcept override {
}
@@ -67,10 +72,9 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
using Thread = nf7::Thread<Runner, Task>;
Queue() = delete;
Queue(Env& env) :
L(luaL_newstate()),
env_(&env),
th_(std::make_shared<Thread>(env, Runner {*this})) {
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() noexcept {
@@ -84,8 +88,8 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete;
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task));
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override {
th_->Push(ctx, std::move(task), t);
}
std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); }

262
file/luajit_inline_node.cc Normal file
View File

@@ -0,0 +1,262 @@
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.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"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
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");
}
class Lambda;
struct Data {
Data() noexcept { }
std::string script;
std::vector<std::string> inputs = {"in"};
std::vector<std::string> outputs = {"out"};
};
InlineNode(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&socket_popup_}),
nf7::DirItem(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();
};
}
InlineNode(nf7::Deserializer& ar) : InlineNode(ar.env()) {
ar(data().script, data().inputs, data().outputs);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().script, data().inputs, data().outputs);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<InlineNode>(env, Data {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;
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
}
void UpdateMenu() noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<InlineNode> life_;
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_;
};
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_) {
}
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;
}
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);
});
} 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:
// synchronized with filesystem
nf7::Life<InlineNode>::Ref file_;
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::Memento::Tag::Id> last_;
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_;
};
std::shared_ptr<nf7::Node::Lambda> InlineNode::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void InlineNode::UpdateMenu() noexcept {
if (ImGui::MenuItem("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
}
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("LuaJIT/InlineNode");
ImGui::SameLine();
if (ImGui::SmallButton("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
nf7::gui::NodeInputSockets(data().inputs);
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &data().script, {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
nf7::gui::NodeOutputSockets(data().outputs);
socket_popup_.Update();
}
void InlineNode::UpdateWidget() noexcept {
ImGui::TextUnformatted("LuaJIT/InlineNode");
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
ImGui::InputTextMultiline("script", &data().script);
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
socket_popup_.Update();
}
}
} // namespace nf7

View File

@@ -15,20 +15,24 @@
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_ref.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_watcher.hh"
#include "common/gui_dnd.hh"
#include "common/lambda.hh"
#include "common/generic_memento.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit_obj.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/node_root_lambda.hh"
#include "common/ptr_selector.hh"
#include "common/task.hh"
#include "common/util_string.hh"
using namespace std::literals;
@@ -37,157 +41,142 @@ using namespace std::literals;
namespace nf7 {
namespace {
class Node final : public nf7::File, public nf7::DirItem, public nf7::Node {
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<Node> kType =
{"LuaJIT/Node", {"DirItem",}};
{"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");
}
class FetchTask;
class Lambda;
Node(Env& env, File::Path&& path = {}, std::string_view desc = "",
std::vector<std::string>&& in = {},
std::vector<std::string>&& out = {}) noexcept :
File(kType, env),
DirItem(DirItem::kMenu | DirItem::kTooltip | DirItem::kDragDropTarget),
log_(std::make_shared<nf7::LoggerRef>()),
obj_(*this, std::move(path)), desc_(desc) {
input_ = std::move(in);
output_ = std::move(out);
}
struct Data {
nf7::FileHolder::Tag obj;
std::string desc;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
};
Node(Env& env, Deserializer& ar) : Node(env) {
ar(obj_, desc_, input_, output_);
Node(Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&obj_, &obj_editor_, &socket_popup_}),
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_);
for (auto itr = input_.begin(); itr < input_.end(); ++itr) {
if (std::find(itr+1, input_.end(), *itr) != input_.end()) {
throw nf7::DeserializeException("duplicated input socket");
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");
}
}
for (auto itr = output_.begin(); itr < output_.end(); ++itr) {
if (std::find(itr+1, output_.end(), *itr) != output_.end()) {
throw nf7::DeserializeException("duplicated output socket");
}
}
}
void Serialize(Serializer& ar) const noexcept override {
ar(obj_, desc_, input_, output_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Node>(
env, File::Path(obj_.path()), desc_,
std::vector<std::string>(input_), std::vector<std::string>(output_));
fu_ = std::nullopt;
};
}
std::shared_ptr<nf7::Lambda> CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
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);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(obj_, data().desc, data().inputs, data().outputs);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Node>(env, Data {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;
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
}
void Handle(const Event&) noexcept override;
void Update() noexcept override;
static void UpdateList(std::vector<std::string>&) noexcept;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateDragDropTarget() noexcept override;
void UpdateNode(Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::GenericWatcher> watcher_;
nf7::Life<Node> life_;
std::shared_ptr<nf7::luajit::Ref> handler_;
nf7::Task<std::shared_ptr<nf7::luajit::Ref>>::Holder fetch_;
const char* popup_ = nullptr;
// persistent params
nf7::FileRef obj_;
std::string desc_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> FetchHandler() noexcept;
void DropHandler() noexcept {
watcher_ = std::nullopt;
handler_ = nullptr;
fetch_ = {};
}
static void Join(std::string& str, const std::vector<std::string>& vec) noexcept {
str.clear();
for (const auto& name : vec) str += name + "\n";
}
static void Split(std::vector<std::string>& vec, const std::string& str) {
vec.clear();
for (size_t i = 0; i < str.size(); ++i) {
auto j = str.find('\n', i);
if (j == std::string::npos) j = str.size();
auto name = str.substr(i, j-i);
File::Path::ValidateTerm(name);
vec.push_back(std::move(name));
i = j;
}
}
};
class Node::FetchTask final : public nf7::Task<std::shared_ptr<nf7::luajit::Ref>> {
public:
FetchTask(Node& target) noexcept :
Task(target.env(), target.id()), target_(&target), log_(target_->log_) {
}
private:
Node* const target_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::FileHolder obj_;
nf7::gui::FileHolderEditor obj_editor_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro Proc() noexcept {
try {
auto& objf = *target_->obj_;
auto& obj = objf.interfaceOrThrow<nf7::luajit::Obj>();
auto handler = co_await obj.Build();
co_yield handler;
nf7::gui::IOSocketListPopup socket_popup_;
try {
*target_->obj_; // checks if objf is alive
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
target_->handler_ = handler;
auto& w = target_->watcher_;
w.emplace(env());
w->Watch(objf.id());
w->AddHandler(Event::kUpdate, [t = target_](auto&) {
if (t->handler_) {
t->log_->Info("detected update of handler object, drops cache");
t->handler_ = nullptr;
}
});
} catch (Exception& e) {
log_->Error("watcher setup failure: "+e.msg());
}
} catch (Exception& e) {
log_->Error("fetch failure: "+e.msg());
}
}
// factory context
std::shared_ptr<nf7::NodeRootLambda> factory_;
std::optional<nf7::Future<nf7::Value>> fu_;
};
class Node::Lambda final : public nf7::Context, public nf7::Lambda,
class Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Node::Lambda> {
public:
Lambda(Node& f, const std::shared_ptr<Owner>& owner) noexcept :
Context(f), nf7::Lambda(owner),
file_(&f), file_id_(f.id()),
log_(f.log_), handler_(f.FetchHandler()) {
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>& caller) noexcept override {
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();
handler_.ThenSub(self, [self, idx, v = std::move(v), caller](auto) mutable {
self->CallHandler({{idx, std::move(v)}}, caller);
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_) {
@@ -198,276 +187,118 @@ class Node::Lambda final : public nf7::Context, public nf7::Lambda,
}
private:
Node* const file_;
File::Id file_id_;
nf7::Life<Node>::Ref f_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> handler_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
std::optional<nf7::luajit::Ref> ctxtable_;
using Param = std::pair<size_t, nf7::Value>;
void CallHandler(std::optional<Param>&& p, const std::shared_ptr<nf7::Lambda>& caller) noexcept
try {
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 handler = handler_.value();
ljq_ = handler->ljq();
env().GetFileOrThrow(file_id_); // check if the owner is alive
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq_, owner(),
[self](auto& th, auto L) { self->HandleThread(th, L); });
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_);
th_.emplace_back(th);
ljq_->Push(self, [this, self, p = std::move(p), caller, handler, th](auto L) mutable {
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);
lua_rawgeti(thL, LUA_REGISTRYINDEX, handler->index());
if (p) {
lua_pushinteger(thL, static_cast<lua_Integer>(p->first));
nf7::luajit::PushValue(thL, p->second);
} else {
lua_pushnil(thL);
lua_pushnil(thL);
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;
}
PushCaller(thL, caller);
PushContextTable(thL);
th->Resume(thL, 4);
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);
});
} catch (nf7::Exception& e) {
log_->Error("failed to call handler: "+e.msg());
}
void HandleThread(nf7::luajit::Thread& th, lua_State* L) noexcept {
switch (th.state()) {
case nf7::luajit::Thread::kFinished:
return;
case nf7::luajit::Thread::kPaused:
log_->Warn("unexpected yield");
ljq_->Push(shared_from_this(),
[th = th.shared_from_this(), L](auto) { th->Resume(L, 0); });
return;
default:
log_->Warn("luajit execution error: "s+lua_tostring(L, -1));
return;
}
}
void PushCaller(lua_State* L, const std::shared_ptr<nf7::Lambda>& caller) noexcept {
constexpr auto kTypeName = "nf7::File/LuaJIT/Node::Owner";
struct D final {
std::weak_ptr<nf7::Lambda> self;
std::shared_ptr<nf7::Lambda> caller;
};
new (lua_newuserdata(L, sizeof(D))) D { .self = weak_from_this(), .caller = caller };
if (luaL_newmetatable(L, kTypeName)) {
lua_pushcfunction(L, [](auto L) {
const auto& d = *reinterpret_cast<D*>(luaL_checkudata(L, 1, kTypeName));
const auto idx = luaL_checkint(L, 2);
auto self = d.self.lock();
if (!self) return luaL_error(L, "context expired");
if (idx < 0) return luaL_error(L, "negative index");
d.caller->Handle(static_cast<size_t>(idx), nf7::luajit::CheckValue(L, 3), self);
return 0;
});
lua_setfield(L, -2, "__call");
lua_pushcfunction(L, [](auto L) {
reinterpret_cast<D*>(luaL_checkudata(L, 1, kTypeName))->~D();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushContextTable(lua_State* L) noexcept {
if (!ctxtable_) {
lua_createtable(L, 0, 0);
lua_pushvalue(L, -1);
const int idx = luaL_ref(L, LUA_REGISTRYINDEX);
ctxtable_.emplace(shared_from_this(), ljq_, idx);
} else {
lua_rawgeti(L, LUA_REGISTRYINDEX, ctxtable_->index());
}
}
};
std::shared_ptr<nf7::Lambda> Node::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept {
return std::make_shared<Node::Lambda>(*this, owner);
}
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::FetchHandler() noexcept {
if (handler_) return handler_;
if (auto fetch = fetch_.lock()) return fetch->fu();
auto fetch = std::make_shared<FetchTask>(*this);
fetch->Start();
fetch_ = {fetch};
return fetch->fu();
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void Node::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
log_->SetUp(*this);
FetchHandler();
return;
case Event::kRemove:
log_->TearDown();
return;
default:
return;
}
}
void Node::Update() noexcept {
const auto& style = ImGui::GetStyle();
const auto em = ImGui::GetFontSize();
if (const char* popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string path;
static std::string desc;
static std::string in, out;
static std::vector<std::string> invec, outvec;
ImGui::TextUnformatted("LuaJIT/Node: config");
if (ImGui::IsWindowAppearing()) {
path = obj_.path().Stringify();
desc = desc_;
Join(in, input_);
Join(out, output_);
}
const auto w = ImGui::CalcItemWidth()/2 - style.ItemSpacing.x/2;
ImGui::InputText("path", &path);
ImGui::InputTextMultiline("description", &desc, {0, 4*em});
ImGui::BeginGroup();
ImGui::TextUnformatted("input:");
ImGui::InputTextMultiline("##input", &in, {w, 0});
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::TextUnformatted("output:");
ImGui::InputTextMultiline("##output", &out, {w, 0});
ImGui::EndGroup();
ImGui::SameLine(0, style.ItemInnerSpacing.x);
ImGui::TextUnformatted("sockets");
bool err = false;
File::Path p;
try {
p = File::Path::Parse(path);
ResolveOrThrow(p);
} catch (File::NotFoundException&) {
ImGui::Bullet(); ImGui::TextUnformatted("path seems to be missing");
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
err = true;
}
try {
Split(invec, in);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid inputs: %s", e.msg().c_str());
err = true;
}
try {
Split(outvec, out);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid outputs: %s", e.msg().c_str());
err = true;
}
if (!err && ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "rebuilding node");
env().ExecMain(ctx, [&, p = std::move(p)]() mutable {
obj_ = std::move(p);
desc_ = std::move(desc);
input_ = std::move(invec);
output_ = std::move(outvec);
Touch();
});
}
ImGui::EndPopup();
}
}
void Node::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
ImGui::Separator();
if (ImGui::MenuItem("try fetch handler")) {
FetchHandler();
}
if (ImGui::MenuItem("drop cached handler")) {
DropHandler();
}
}
void Node::UpdateTooltip() noexcept {
ImGui::Text("path : %s", obj_.path().Stringify().c_str());
ImGui::Text("handler: %s", handler_? "ready": "no");
ImGui::Text("factory:");
ImGui::Indent();
obj_editor_.Tooltip();
ImGui::Unindent();
ImGui::Spacing();
ImGui::Text("input:");
ImGui::Indent();
for (const auto& name : input_) {
for (const auto& name : data().inputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (input_.empty()) {
if (data().inputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("output:");
ImGui::Indent();
for (const auto& name : output_) {
for (const auto& name : data().outputs) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (output_.empty()) {
if (data().outputs.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("description:");
ImGui::Indent();
if (desc_.empty()) {
if (data().desc.empty()) {
ImGui::TextDisabled("(empty)");
} else {
ImGui::TextUnformatted(desc_.c_str());
ImGui::TextUnformatted(data().desc.c_str());
}
ImGui::Unindent();
}
void Node::UpdateWidget() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextDisabled("drop a file here to set it as source");
}
void Node::UpdateDragDropTarget() noexcept {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
obj_ = std::move(*p);
ImGui::TextUnformatted("LuaJIT/Node: config");
obj_editor_.ButtonWithLabel("obj factory");
ImGui::InputTextMultiline("description", &data().desc, {0, 4*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
}
void Node::UpdateNode(Node::Editor&) noexcept {
if (ImGui::Button("I/O list")) {
socket_popup_.Open(data().inputs, data().outputs);
}
ImGui::Spacing();
obj_editor_.ItemWidget("obj factory");
socket_popup_.Update();
}
}

View File

@@ -1,336 +0,0 @@
#include <atomic>
#include <exception>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/dir_item.hh"
#include "common/file_ref.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/generic_watcher.hh"
#include "common/gui_dnd.hh"
#include "common/lock.hh"
#include "common/luajit.hh"
#include "common/luajit_obj.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_thread.hh"
#include "common/logger_ref.hh"
#include "common/ptr_selector.hh"
#include "common/task.hh"
#include "common/yas_nf7.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Obj final : public nf7::File,
public nf7::DirItem,
public nf7::luajit::Obj {
public:
static inline const GenericTypeInfo<Obj> kType = {"LuaJIT/Obj", {"DirItem",}};
static constexpr size_t kMaxSize = 1024*1024*16; /* = 16 MiB */
class ExecTask;
Obj(Env& env, Path&& path = {}) noexcept :
File(kType, env),
DirItem(DirItem::kTooltip | DirItem::kMenu | DirItem::kDragDropTarget),
log_(std::make_shared<nf7::LoggerRef>()),
src_(*this, std::move(path)) {
}
Obj(Env& env, Deserializer& ar) noexcept : Obj(env) {
ar(src_);
}
void Serialize(Serializer& ar) const noexcept override {
ar(src_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Obj>(env, Path(src_.path()));
}
void Handle(const Event&) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateDragDropTarget() noexcept override;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::luajit::Obj>(t).Select(this);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::GenericWatcher> watcher_;
std::shared_ptr<nf7::luajit::Ref> cache_;
nf7::Task<std::shared_ptr<nf7::luajit::Ref>>::Holder exec_;
const char* popup_ = nullptr;
// persistent params
nf7::FileRef src_;
void Reset() noexcept;
};
class Obj::ExecTask final : public nf7::Task<std::shared_ptr<nf7::luajit::Ref>> {
public:
ExecTask(Obj& target) noexcept :
Task(target.env(), target.id()), target_(&target), log_(target_->log_) {
}
size_t GetMemoryUsage() const noexcept override {
return buf_size_;
}
private:
Obj* target_;
std::shared_ptr<nf7::LoggerRef> log_;
std::string chunkname_;
std::atomic<size_t> buf_size_ = 0;
std::vector<uint8_t> buf_;
bool buf_consumed_ = false;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro Proc() noexcept override {
try {
auto& srcf = *target_->src_;
chunkname_ = srcf.abspath().Stringify();
// acquire lock of source
auto src = srcf.interfaceOrThrow<nf7::AsyncBuffer>().self();
auto srclock = co_await src->AcquireLock(false);
log_->Trace("source file lock acquired");
// get size of source
buf_size_ = co_await src->size();
if (buf_size_ == 0) {
throw nf7::Exception("source is empty");
}
if (buf_size_ > kMaxSize) {
throw nf7::Exception("source is too huge");
}
// read source
buf_.resize(buf_size_);
const size_t read = co_await src->Read(0, buf_.data(), buf_size_);
if (read != buf_size_) {
throw nf7::Exception("failed to read all bytes from source");
}
// create thread to compile lua script
auto ljq = target_->
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
nf7::Future<int>::Promise lua_pro(self());
auto handler = nf7::luajit::Thread::CreatePromiseHandler<int>(
lua_pro, [&](auto L) {
if (lua_gettop(L) != 1) {
throw nf7::Exception("expected one object to be returned");
}
if (auto str = lua_tostring(L, -1)) {
log_->Info("got '"s+str+"'");
} else {
log_->Info("got ["s+lua_typename(L, lua_type(L, -1))+"]");
}
return luaL_ref(L, LUA_REGISTRYINDEX);
});
// setup watcher
try {
*target_->src_; // check if the src is alive
auto& w = target_->watcher_;
w.emplace(env());
w->Watch(srcf.id());
std::weak_ptr<Task> wself = self();
w->AddHandler(Event::kUpdate, [t = target_, wself](auto&) {
if (auto self = wself.lock()) {
t->log_->Info("detected update of source file, aborts building");
t->exec_ = {};
} else if (t->cache_) {
t->log_->Info("detected update of source file, drops cache automatically");
t->cache_ = nullptr;
t->Touch();
}
});
} catch (Exception& e) {
log_->Warn("watcher setup error: "+e.msg());
}
// queue task to trigger the thread
auto la_owner = std::make_shared<nf7::Lambda::Owner>(
target_->abspath(), "building Lua object", nullptr);
auto th = std::make_shared<nf7::luajit::Thread>(
self(), ljq, la_owner, std::move(handler));
th->Install(log_);
ljq->Push(self(), [&](auto L) {
try {
auto thL = th->Init(L);
Compile(thL);
th->Resume(thL, 0);
} catch (Exception&) {
lua_pro.Throw(std::current_exception());
}
});
// wait for end of execution and return built object's index
const int idx = co_await lua_pro.future();
log_->Trace("task finished");
// context for object cache
// TODO use specific Context type
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator(), "luajit object cache");
// return the object and cache it
target_->cache_ = std::make_shared<nf7::luajit::Ref>(ctx, ljq, idx);
co_yield target_->cache_;
} catch (Exception& e) {
log_->Error(e.msg());
throw;
}
}
void Compile(lua_State* L) {
static const auto kReader = [](lua_State*, void* selfptr, size_t* size) -> const char* {
auto self = reinterpret_cast<ExecTask*>(selfptr);
if (std::exchange(self->buf_consumed_, true)) {
*size = 0;
return nullptr;
} else {
*size = self->buf_.size();
return reinterpret_cast<const char*>(self->buf_.data());
}
};
if (0 != lua_load(L, kReader, this, chunkname_.c_str())) {
throw nf7::Exception(lua_tostring(L, -1));
}
}
};
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Obj::Build() noexcept {
if (auto exec = exec_.lock()) return exec->fu();
if (cache_) return std::shared_ptr<nf7::luajit::Ref>{cache_};
auto exec = std::make_shared<ExecTask>(*this);
exec->Start();
exec_ = {exec};
return exec->fu();
}
void Obj::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
log_->SetUp(*this);
break;
case Event::kRemove:
exec_ = {};
cache_ = nullptr;
watcher_ = std::nullopt;
log_->TearDown();
break;
default:
break;
}
}
void Obj::Reset() noexcept {
exec_ = {};
cache_ = nullptr;
watcher_ = std::nullopt;
}
void Obj::Update() noexcept {
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string path_str;
ImGui::TextUnformatted("LuaJIT/Obj: config");
if (ImGui::IsWindowAppearing()) {
path_str = src_.path().Stringify();
}
const bool submit = ImGui::InputText(
"path", &path_str, ImGuiInputTextFlags_EnterReturnsTrue);
Path path;
bool err = false;
try {
path = Path::Parse(path_str);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
err = true;
}
try {
ResolveOrThrow(path);
} catch (File::NotFoundException&) {
ImGui::Bullet(); ImGui::Text("(target seems to be missing)");
}
if (!err) {
if (ImGui::Button("ok") || submit) {
ImGui::CloseCurrentPopup();
if (path != src_.path()) {
auto task = [this, p = std::move(path)]() mutable {
src_ = std::move(p);
Reset();
};
auto ctx = std::make_shared<
nf7::GenericContext>(*this, "changing source path");
env().ExecMain(ctx, std::move(task));
}
}
}
ImGui::EndPopup();
}
}
void Obj::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
ImGui::Separator();
if (ImGui::MenuItem("try build")) {
Build();
}
if (ImGui::MenuItem("drop cache", nullptr, nullptr, !!cache_)) {
Reset();
}
}
void Obj::UpdateTooltip() noexcept {
ImGui::Text("source: %s", src_.path().Stringify().c_str());
ImGui::Text("cache : %d", cache_? cache_->index(): -1);
ImGui::TextDisabled("drop a file here to set it as source");
}
void Obj::UpdateDragDropTarget() noexcept {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
src_ = std::move(*p);
}
}
}
} // namespace nf7

View File

@@ -20,214 +20,133 @@
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/gui_resizer.hh"
#include "common/gui_value.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
#include "common/yas_imgui.hh"
namespace nf7 {
namespace {
class Imm final : public nf7::File, public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<Imm> kType =
{"Node/Imm", {"DirItem", "Node"}};
static inline const nf7::GenericTypeInfo<Imm> kType =
{"Node/Imm", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Emits an immediate value when get an input.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class Lambda;
enum Type { kPulse, kInteger, kScalar, kScalarNormal, kStringText, };
static inline const std::vector<std::pair<Type, const char*>> kTypeNames = {
{kPulse, "pulse"},
{kInteger, "integer"},
{kScalar, "scalar"},
{kScalarNormal, "scalar/normal"},
{kStringText, "string/text"},
};
Imm(Env& env, Type type = kPulse, nf7::Value&& v = {}) noexcept :
File(kType, env), DirItem(DirItem::kNone), mem_(*this, {type, std::move(v)}) {
output_ = {"out"};
Imm(nf7::Env& env, nf7::gui::Value&& v = {}) noexcept :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
life_(*this), mem_(std::move(v), *this) {
}
Imm(Env& env, Deserializer& ar) : Imm(env) {
auto& data = mem_.data();
std::string typestr;
ar(typestr, data.value, data.size);
ChangeType(ParseType(typestr));
mem_.CommitAmend();
Imm(nf7::Deserializer& ar) : Imm(ar.env()) {
ar(mem_.data());
}
void Serialize(Serializer& ar) const noexcept override {
const auto& data = mem_.data();
ar(std::string_view{StringifyType(data.type)}, data.value, data.size);
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
const auto& data = mem_.data();
return std::make_unique<Imm>(env, data.type, nf7::Value{data.value});
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Imm>(env, nf7::gui::Value {mem_.data()});
}
std::shared_ptr<nf7::Lambda> CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
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 {
static const std::vector<std::string> kInputs = {"in"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"out"};
return kOutputs;
}
void UpdateNode(Node::Editor&) noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
struct Data final {
public:
Data(Type t, nf7::Value&& v) noexcept : type(t), value(std::move(v)) {
}
Type type;
nf7::Value value;
ImVec2 size;
};
nf7::GenericMemento<Data> mem_;
nf7::Life<Imm> life_;
void UpdateEditor(Node::Editor&, float w) noexcept;
void ChangeType(Type) noexcept;
static const char* StringifyType(Type t) noexcept {
auto itr = std::find_if(kTypeNames.begin(), kTypeNames.end(),
[t](auto& x) { return x.first == t; });
assert(itr != kTypeNames.end());
return itr->second;
}
static Type ParseType(std::string_view v) {
auto itr = std::find_if(kTypeNames.begin(), kTypeNames.end(),
[v](auto& x) { return x.second == v; });
if (itr == kTypeNames.end()) {
throw nf7::DeserializeException("unknown Node/Imm type");
}
return itr->first;
}
nf7::GenericMemento<nf7::gui::Value> mem_;
};
class Imm::Lambda final : public nf7::Lambda,
class Imm::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Imm::Lambda> {
public:
Lambda(Imm& f, const std::shared_ptr<Owner>& owner) noexcept :
nf7::Lambda(owner), value_(f.mem_.data().value) {
Lambda(Imm& f, const std::shared_ptr<Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(size_t, nf7::Value&&, const std::shared_ptr<nf7::Lambda>& recv) noexcept override {
recv->Handle(0, nf7::Value {value_}, shared_from_this());
void Handle(std::string_view name, const nf7::Value&,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override {
if (!f_) return;
if (name == "in") {
caller->Handle("out", f_->mem_.data().entity(), shared_from_this());
return;
}
}
private:
nf7::Value value_;
nf7::Life<Imm>::Ref f_;
};
std::shared_ptr<nf7::Lambda> Imm::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept {
return std::make_shared<Imm::Lambda>(*this, owner);
std::shared_ptr<nf7::Node::Lambda> Imm::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Imm::Lambda>(*this, parent);
}
void Imm::UpdateNode(Node::Editor& editor) noexcept {
const auto& style = ImGui::GetStyle();
auto& data = mem_.data();
const auto left = ImGui::GetCursorPosX();
ImGui::TextUnformatted("Node/Imm");
ImGui::SameLine();
ImGui::SmallButton(StringifyType(data.type));
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
for (const auto& t : kTypeNames) {
if (ImGui::MenuItem(t.second, nullptr, t.first == data.type)) {
if (t.first != data.type) {
ChangeType(t.first);
mem_.Commit();
Touch();
}
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
const auto right = ImGui::GetCursorPosX() - style.ItemSpacing.x;
ImGui::NewLine();
ImGui::PushItemWidth(right-left);
if (ImNodes::BeginOutputSlot("out", 1)) {
UpdateEditor(editor, right-left);
ImGui::SameLine();
gui::NodeSocket();
ImNodes::EndSlot();
}
ImGui::PopItemWidth();
}
void Imm::UpdateEditor(Node::Editor&, float w) noexcept {
static const double kZero = 0., kOne = 1.;
void Imm::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
bool mod = false, com = false;
bool mod = false;
ImGui::TextUnformatted("Node/Imm");
ImGui::SameLine();
mod |= mem_.data().UpdateTypeButton(nullptr, true);
auto& d = mem_.data();
auto& v = d.value;
switch (d.type) {
case kPulse:
ImGui::Dummy({w, em});
break;
case kInteger:
mod = ImGui::DragScalar("##integer", ImGuiDataType_S64, &v.integer());
com = ImGui::IsItemDeactivatedAfterEdit();
break;
case kScalar:
mod = ImGui::DragScalar("##scalar", ImGuiDataType_Double, &v.scalar());
com = ImGui::IsItemDeactivatedAfterEdit();
break;
case kScalarNormal:
mod = ImGui::SliderScalar("##scalar_normal", ImGuiDataType_Double, &v.scalar(), &kZero, &kOne);
com = ImGui::IsItemDeactivatedAfterEdit();
break;
case kStringText:
if (gui::Resizer(&d.size, {w/em, 3}, {128, 128}, em)) {
const auto& psize = mem_.last().size;
mod |= psize.x != d.size.x || psize.y != d.size.y;
com |= mod;
}
mod |= ImGui::InputTextMultiline("##string_text", &v.string(), d.size*em);
com |= ImGui::IsItemDeactivatedAfterEdit();
break;
if (ImNodes::BeginInputSlot("in", 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
ImGui::SameLine();
ImGui::PushItemWidth(8*em);
mod |= mem_.data().UpdateEditor();
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImNodes::BeginOutputSlot("out", 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
if (com) {
if (mod) {
mem_.Commit();
} else if (mod) {
Touch();
}
}
void Imm::ChangeType(Type t) noexcept {
auto& d = mem_.data();
d.type = t;
switch (d.type) {
case kPulse:
d.value = nf7::Value::Pulse{};
break;
case kInteger:
if (!d.value.isInteger()) {
d.value = nf7::Value::Integer{0};
}
break;
case kScalar:
case kScalarNormal:
if (!d.value.isScalar()) {
d.value = nf7::Value::Scalar{0.};
}
break;
case kStringText:
if (!d.value.isString()) {
d.value = nf7::Value::String{""};
}
break;
void Imm::UpdateWidget() noexcept {
ImGui::TextUnformatted("Node/Imm");
if (mem_.data().UpdateEditor()) {
mem_.Commit();
}
}
}
} // namespace nf7

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
@@ -14,13 +15,16 @@
#include "nf7.hh"
#include "common/file_ref.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/generic_watcher.hh"
#include "common/gui_dnd.hh"
#include "common/gui_node.hh"
#include "common/lambda.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/node.hh"
@@ -30,112 +34,156 @@
namespace nf7 {
namespace {
class Ref final : public nf7::File, public nf7::Node {
class Ref final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Ref> kType =
{"Node/Ref", {"Node"}};
static inline const nf7::GenericTypeInfo<Ref> kType = {
"Node/Ref", {"nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Refers other Node.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted(
"the referencee's changes won't be applied to active lambdas "
"until their recreation");
ImGui::Bullet(); ImGui::TextUnformatted(
"press 'sync' button on Node UI to resolve socket issues");
}
class Lambda;
Ref(Env& env, Path&& path = {"initial", "path"},
std::vector<std::string>&& in = {},
std::vector<std::string>&& out = {}) noexcept :
File(kType, env),
log_(std::make_shared<nf7::LoggerRef>()),
mem_(*this, {*this, std::move(path), std::move(in), std::move(out)}) {
struct Data final {
public:
nf7::File::Path target;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
};
Ref(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}),
nf7::Node(nf7::Node::kCustomNode | nf7::Node::kMenu),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this),
config_popup_(*this) {
nf7::FileBase::Install(*log_);
mem_.onRestore = mem_.onCommit = [this]() { SetUpWatcher(); };
}
Ref(Env& env, Deserializer& ar) : Ref(env) {
auto& d = mem_.data();
ar(d.target, d.input, d.output);
Ref(nf7::Deserializer& ar) : Ref(ar.env()) {
ar(data().target, data().inputs, data().outputs);
}
void Serialize(Serializer& ar) const noexcept override {
const auto& d = mem_.data();
ar(d.target, d.input, d.output);
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(data().target, data().inputs, data().outputs);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
const auto& d = mem_.data();
return std::make_unique<Ref>(
env, Path{d.target.path()},
std::vector<std::string>{input_}, std::vector<std::string>{output_});
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Ref>(env, Data {data()});
}
std::shared_ptr<nf7::Lambda> CreateLambda(const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
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;
}
std::span<const std::string> GetOutputs() const noexcept override {
return data().outputs;
}
void Handle(const Event& ev) noexcept {
const auto& d = mem_.data();
void Handle(const nf7::File::Event& ev) noexcept {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case Event::kAdd:
log_->SetUp(*this);
/* fallthrough */
case Event::kUpdate:
input_ = d.input;
output_ = d.output;
return;
case Event::kRemove:
log_->TearDown();
return;
case nf7::File::Event::kAdd:
env().ExecMain(std::make_shared<nf7::GenericContext>(*this),
std::bind(&Ref::SetUpWatcher, this));
break;
default:
return;
break;
}
}
void Update() noexcept override;
void UpdateNode(Node::Editor&) noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateMenu(nf7::Node::Editor&) 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::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<Ref> life_;
std::shared_ptr<nf7::LoggerRef> log_;
const char* popup_ = nullptr;
std::optional<nf7::GenericWatcher> watcher_;
// persistent params
struct Data final {
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
// GUI popup
class ConfigPopup final : public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
Data(Ref& owner, Path&& p,
std::vector<std::string>&& in,
std::vector<std::string>&& out) noexcept :
target(owner, std::move(p)), input(std::move(in)), output(std::move(out)) {
ConfigPopup(Ref& f) noexcept : nf7::gui::Popup("ConfigPopup"), f_(&f) {
}
nf7::FileRef target;
std::vector<std::string> input;
std::vector<std::string> output;
};
nf7::GenericMemento<Data> mem_;
void Open() noexcept {
path_ = f_->data().target.Stringify();
nf7::gui::Popup::Open();
}
void Update() noexcept override;
private:
Ref* const f_;
std::string path_;
} config_popup_;
void SyncQuiet() noexcept {
auto& d = mem_.data();
// accessors
nf7::File& target() const {
auto& f = ResolveOrThrow(data().target);
if (&f == this) throw nf7::Exception("self reference");
return f;
}
// socket synchronization
bool SyncQuiet() noexcept {
auto& dsti = data().inputs;
auto& dsto = data().outputs;
bool mod = false;
try {
auto& n = target();
auto& n = target().interfaceOrThrow<nf7::Node>();
const auto i = n.input();
d.input = std::vector<std::string>{i.begin(), i.end()};
const auto srci = n.GetInputs();
mod |= std::equal(dsti.begin(), dsti.end(), srci.begin(), srci.end());
dsti = std::vector<std::string>{srci.begin(), srci.end()};
const auto o = n.output();
d.output = std::vector<std::string>{o.begin(), o.end()};
const auto srco = n.GetOutputs();
mod |= std::equal(dsto.begin(), dsto.end(), srco.begin(), srco.end());
dsto = std::vector<std::string>{srco.begin(), srco.end()};
} catch (nf7::Exception& e) {
d.input = {};
d.output = {};
mod = dsti.size() > 0 || dsto.size() > 0;
dsti = {};
dsto = {};
log_->Error("failed to sync: "+e.msg());
}
return mod;
}
void Sync() noexcept {
SyncQuiet();
const auto& d = mem_.data();
if (input_ != d.input || output_ != d.output) {
if (SyncQuiet()) {
mem_.Commit();
}
}
void ExecSync() noexcept {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "synchornizing"),
[this]() { Sync(); });
}
void ExecChangePath(Path&& p) noexcept {
// referencee operation
void ExecChangeTarget(Path&& p) noexcept {
auto& target = mem_.data().target;
if (p == target.path()) return;
if (p == target) return;
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "change path"),
[this, &target, p = std::move(p)]() mutable {
@@ -145,126 +193,81 @@ class Ref final : public nf7::File, public nf7::Node {
});
}
nf7::Node& target() const {
auto& f = *mem_.data().target;
if (&f == this) throw nf7::Exception("self reference");
return f.interfaceOrThrow<nf7::Node>();
// target watcher
void SetUpWatcher() noexcept
try {
watcher_ = std::nullopt;
const auto id = target().id();
assert(id);
watcher_.emplace(env());
watcher_->AddHandler(nf7::File::Event::kUpdate, [this](auto&) { Touch(); });
watcher_->Watch(id);
} catch (nf7::Exception&) {
}
};
class Ref::Lambda final : public nf7::Lambda,
class Ref::Lambda final : public Node::Lambda,
public std::enable_shared_from_this<Ref::Lambda> {
public:
Lambda(Ref& f,
std::shared_ptr<nf7::Lambda>&& base,
const std::shared_ptr<nf7::Lambda::Owner>& owner) :
nf7::Lambda(owner), base_(std::move(base)), log_(f.log_) {
auto& n = f.target();
static constexpr size_t kMaxDepth = 1024;
// ref input index -> target input index
inmap_.reserve(f.input_.size());
for (const auto& name : f.input()) {
try {
inmap_.push_back(n.input(name));
} catch (nf7::Exception&){
inmap_.push_back(std::nullopt);
}
}
// target output index -> ref output index
outmap_.reserve(f.output_.size());
for (const auto& name : n.output()) {
try {
outmap_.push_back(f.output(name));
} catch (nf7::Exception&){
outmap_.push_back(std::nullopt);
}
}
Lambda(Ref& f, const std::shared_ptr<Node::Lambda>& parent) :
Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
}
void Handle(size_t idx, Value&& v, const std::shared_ptr<nf7::Lambda>& caller) noexcept override
void Handle(std::string_view name, const Value& v,
const std::shared_ptr<Node::Lambda>& caller) noexcept override
try {
auto parent = parent_.lock();
if (parent && caller == base_) {
parent->Handle(GetIndex(outmap_, idx), std::move(v), shared_from_this());
} else {
assert(!parent || parent == caller);
parent_ = caller;
base_->Handle(GetIndex(inmap_, idx), std::move(v), shared_from_this());
if (!f_) return;
auto parent = this->parent();
if (!parent) return;
if (caller == base_) {
parent->Handle(name, v, shared_from_this());
}
if (caller == parent) {
if (!base_) {
if (depth() > kMaxDepth) {
log_->Error("stack overflow");
return;
}
base_ = f_->target().
interfaceOrThrow<nf7::Node>().
CreateLambda(shared_from_this());
}
base_->Handle(name, v, shared_from_this());
}
} catch (nf7::Exception& e) {
log_->Error("failed to call referencee: "+e.msg());
}
void Abort() noexcept override {
if (base_) {
base_->Abort();
}
} catch (nf7::Exception&) {
log_->Warn("ignored unknown IO");
}
private:
std::shared_ptr<nf7::Lambda> base_;
nf7::Life<Ref>::Ref f_;
std::shared_ptr<nf7::LoggerRef> log_;
std::weak_ptr<nf7::Lambda> parent_;
std::vector<std::optional<size_t>> inmap_, outmap_;
static size_t GetIndex(const std::vector<std::optional<size_t>>& map, size_t idx) {
if (idx >= map.size() || !map[idx]) {
throw nf7::Exception("got unexpected IO index");
}
return *map[idx];
}
std::shared_ptr<Node::Lambda> base_;
};
std::shared_ptr<nf7::Lambda> Ref::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept
std::shared_ptr<Node::Lambda> Ref::CreateLambda(
const std::shared_ptr<Node::Lambda>& parent) noexcept
try {
auto self = std::make_shared<nf7::Lambda::Owner>(
abspath(), "call through reference", owner);
return std::make_shared<Ref::Lambda>(*this, target().CreateLambda(self), owner);
return std::make_shared<Ref::Lambda>(*this, parent);
} catch (nf7::Exception& e) {
log_->Error("failed to create lambda: "+e.msg());
return nullptr;
}
void Ref::Update() noexcept {
const auto& d = mem_.data();
if (auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string pathstr;
if (ImGui::IsWindowAppearing()) {
pathstr = d.target.path().Stringify();
}
ImGui::TextUnformatted("Node/Ref: config");
const bool submit = ImGui::InputText(
"path", &pathstr, ImGuiInputTextFlags_EnterReturnsTrue);
bool err = false;
Path path;
try {
path = Path::Parse(pathstr);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
err = true;
}
try {
ResolveOrThrow(path);
} catch (File::NotFoundException&) {
ImGui::Bullet(); ImGui::Text("target seems to be missing");
}
if (!err && (ImGui::Button("ok") || submit)) {
ImGui::CloseCurrentPopup();
ExecChangePath(std::move(path));
}
ImGui::EndPopup();
}
}
void Ref::UpdateNode(Node::Editor&) noexcept {
const auto& style = ImGui::GetStyle();
const auto em = ImGui::GetFontSize();
@@ -272,12 +275,10 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ImGui::TextUnformatted("Node/Ref");
ImGui::SameLine();
if (ImGui::SmallButton("sync")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "synchornizing with target node"),
[this]() { Sync(); });
ExecSync();
}
const auto pathstr = mem_.data().target.path().Stringify();
const auto pathstr = mem_.data().target.Stringify();
auto w = 6*em;
{
@@ -285,29 +286,29 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
w = std::max(w, std::min(pw, 8*em));
auto iw = 3*em;
for (const auto& v : input_) {
for (const auto& v : data().inputs) {
iw = std::max(iw, ImGui::CalcTextSize(v.c_str()).x);
}
auto ow = 3*em;
for (const auto& v : output_) {
for (const auto& v : data().outputs) {
ow = std::max(ow, ImGui::CalcTextSize(v.c_str()).x);
}
w = std::max(w, 1*em+style.ItemSpacing.x+iw +1*em+ ow+style.ItemSpacing.x+1*em);
}
if (ImGui::Button(pathstr.c_str(), {w, 0})) {
popup_ = "ConfigPopup";
config_popup_.Open();
}
if (ImGui::BeginDragDropTarget()) {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
ExecChangePath(std::move(*p));
ExecChangeTarget(std::move(*p));
}
ImGui::EndDragDropTarget();
}
const auto right = ImGui::GetCursorPosX() + w;
ImGui::BeginGroup();
for (const auto& name : input_) {
for (const auto& name : data().inputs) {
if (ImNodes::BeginInputSlot(name.c_str(), 1)) {
gui::NodeSocket();
ImGui::SameLine();
@@ -318,7 +319,7 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
for (const auto& name : output_) {
for (const auto& name : data().outputs) {
const auto tw = ImGui::CalcTextSize(name.c_str()).x;
ImGui::SetCursorPosX(right-(tw+style.ItemSpacing.x+em));
@@ -330,6 +331,69 @@ void Ref::UpdateNode(Node::Editor&) noexcept {
}
}
ImGui::EndGroup();
config_popup_.Update();
}
void Ref::UpdateMenu(nf7::Node::Editor& ed) noexcept {
if (ImGui::MenuItem("sync")) {
ExecSync();
}
if (ImGui::MenuItem("replace target")) {
config_popup_.Open();
}
try {
auto& f = target();
auto& n = f.interfaceOrThrow<nf7::Node>();
auto d = f.interface<nf7::DirItem>();
const bool dmenu = n.flags() & nf7::Node::kMenu_DirItem;
const bool menu = n.flags() & nf7::Node::kMenu;
if ((dmenu || menu) && ImGui::BeginMenu("target")) {
if (dmenu) {
assert(d);
ImGui::Separator();
d->UpdateMenu();
}
if (menu) {
ImGui::Separator();
n.UpdateMenu(ed);
}
ImGui::EndMenu();
}
} catch (nf7::Exception&) {
}
}
void Ref::ConfigPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::TextUnformatted("Node/Ref: config");
const bool submit = ImGui::InputText(
"path", &path_, ImGuiInputTextFlags_EnterReturnsTrue);
bool err = false;
Path path;
try {
path = Path::Parse(path_);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
err = true;
}
try {
f_->ResolveOrThrow(path).interfaceOrThrow<nf7::Node>();
} catch (nf7::File::NotFoundException&) {
ImGui::Bullet(); ImGui::Text("target seems to be missing");
} catch (nf7::File::NotImplementedException&) {
ImGui::Bullet(); ImGui::Text("target doesn't seem to have Node interface");
}
if (!err && (ImGui::Button("ok") || submit)) {
ImGui::CloseCurrentPopup();
f_->ExecChangeTarget(std::move(path));
}
ImGui::EndPopup();
}
}
}

409
file/sequencer_adaptor.cc Normal file
View File

@@ -0,0 +1,409 @@
#include "nf7.hh"
#include <cassert>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_value.hh"
#include "common/life.hh"
#include "common/ptr_selector.hh"
#include "common/sequencer.hh"
#include "common/value.hh"
namespace nf7 {
namespace {
class Adaptor final : public nf7::FileBase, public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Adaptor> kType =
{"Sequencer/Adaptor", {"nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Wraps and Adapts other Sequencer.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class Session;
class Lambda;
class Editor;
struct Var {
std::string name;
bool peek = false;
void serialize(auto& ar) {
ar(name, peek);
}
};
struct Data {
nf7::FileHolder::Tag target;
std::vector<std::pair<std::string, nf7::gui::Value>> input_imm;
std::vector<std::pair<std::string, Var>> input_map;
std::vector<std::pair<std::string, std::string>> output_map;
};
Adaptor(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&target_, &target_editor_}),
Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip |
Sequencer::kParamPanel),
life_(*this),
target_(*this, "target", mem_),
target_editor_(target_,
[](auto& t) { return t.flags().contains("nf7::Sequencer"); }),
mem_(std::move(data), *this) {
mem_.data().target.SetTarget(target_);
mem_.CommitAmend();
}
Adaptor(nf7::Deserializer& ar) : Adaptor(ar.env()) {
ar(target_, data().input_imm, data().input_map, data().output_map);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(target_, data().input_imm, data().input_map, data().output_map);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Adaptor>(env, Data {data()});
}
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override;
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
void UpdateParamPanel(nf7::Sequencer::Editor&) noexcept override;
void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
}
private:
nf7::Life<Adaptor> life_;
nf7::FileHolder target_;
nf7::gui::FileHolderEditor target_editor_;
nf7::GenericMemento<Data> mem_;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
};
class Adaptor::Session final : public nf7::Sequencer::Session {
public:
// ensure that Adaptor is alive
Session(Adaptor& f, const std::shared_ptr<nf7::Sequencer::Session>& parent) noexcept : parent_(parent) {
for (auto& p : f.data().input_imm) {
vars_[p.first] = p.second.entity();
}
for (auto& p : f.data().input_map) {
if (p.second.name.size() == 0) continue;
if (p.second.peek) {
if (const auto ptr = parent->Peek(p.second.name)) {
vars_[p.first] = *ptr;
}
} else {
if (auto ptr = parent->Receive(p.second.name)) {
vars_[p.first] = std::move(*ptr);
}
}
}
for (auto& p : f.data().output_map) {
outs_[p.first] = p.second;
}
}
const nf7::Value* Peek(std::string_view name) noexcept override {
auto itr = vars_.find(std::string {name});
return itr != vars_.end()? &itr->second: nullptr;
}
std::optional<nf7::Value> Receive(std::string_view name) noexcept override {
auto itr = vars_.find(std::string {name});
if (itr == vars_.end()) {
return std::nullopt;
}
auto ret = std::move(itr->second);
vars_.erase(itr);
return ret;
}
void Send(std::string_view name, nf7::Value&& v) noexcept override {
if (done_) return;
auto itr = outs_.find(std::string {name});
if (itr != outs_.end()) {
parent_->Send(itr->second, std::move(v));
}
}
void Finish() noexcept override {
assert(parent_);
parent_->Finish();
done_ = true;
}
private:
std::shared_ptr<nf7::Sequencer::Session> parent_;
std::unordered_map<std::string, nf7::Value> vars_;
std::unordered_map<std::string, std::string> outs_;
bool done_ = false;
};
class Adaptor::Lambda final : public nf7::Sequencer::Lambda,
public std::enable_shared_from_this<Adaptor::Lambda> {
public:
Lambda(Adaptor& f, const std::shared_ptr<nf7::Context>& parent) noexcept :
nf7::Sequencer::Lambda(f, parent), f_(f.life_) {
}
void Run(const std::shared_ptr<nf7::Sequencer::Session>& ss) noexcept override
try {
f_.EnforceAlive();
auto& target = f_->target_.GetFileOrThrow();
auto& seq = target.interfaceOrThrow<nf7::Sequencer>();
if (!la_ || target.id() != cached_id_) {
la_ = seq.CreateLambda(shared_from_this());
cached_id_ = target.id();
}
la_->Run(std::make_shared<Adaptor::Session>(*f_, ss));
} catch (nf7::Exception&) {
ss->Finish();
}
private:
nf7::Life<Adaptor>::Ref f_;
nf7::File::Id cached_id_ = 0;
std::shared_ptr<nf7::Sequencer::Lambda> la_;
};
std::shared_ptr<nf7::Sequencer::Lambda> Adaptor::CreateLambda(
const std::shared_ptr<nf7::Context>& parent) noexcept {
return std::make_shared<Adaptor::Lambda>(*this, parent);
}
class Adaptor::Editor final : public nf7::Sequencer::Editor {
public:
using nf7::Sequencer::Editor::Editor;
};
void Adaptor::UpdateItem(Sequencer::Editor&) noexcept {
try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kCustomItem) {
Adaptor::Editor ed;
seq.UpdateItem(ed);
}
} catch (nf7::Exception&) {
ImGui::Text("%s", target_editor_.GetDisplayText().c_str());
}
}
void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept {
bool commit = false;
auto& imm = data().input_imm;
auto& inputs = data().input_map;
auto& outputs = data().output_map;
const auto em = ImGui::GetFontSize();
if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) {
target_editor_.ButtonWithLabel("target");
if (ImGui::BeginTable("table", 3)) {
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f);
ImGui::TableSetupColumn("arrow", ImGuiTableColumnFlags_WidthFixed, 1*em);
ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthStretch, 1.f);
// ---- immediate values
ImGui::PushID("imm");
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("imm input");
ImGui::SameLine();
if (ImGui::Button("+")) {
imm.push_back({"target_input", {}});
commit = true;
}
if (imm.size() == 0) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextDisabled("no rule");
}
for (size_t i = 0; i < imm.size(); ++i) {
auto& p = imm[i];
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
if (ImGui::TableNextColumn()) {
commit |= p.second.UpdateTypeButton("T");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
commit |= p.second.UpdateEditor();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("->");
}
if (ImGui::TableNextColumn()) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##name", "dst", &p.first);
commit |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::PopID();
}
ImGui::PopID();
// ---- input map
ImGui::PushID("input");
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("input");
ImGui::SameLine();
if (ImGui::Button("+")) {
inputs.push_back({"target_input", {}});
commit = true;
}
if (inputs.size() == 0) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextDisabled("no rule");
}
for (size_t i = 0; i < inputs.size(); ++i) {
auto& in = inputs[i];
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
if (ImGui::TableNextColumn()) {
const char* text = in.second.peek? "P": "R";
if (ImGui::Button(text)) {
in.second.peek = !in.second.peek;
commit = true;
}
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##src", "src", &in.second.name);
commit |= ImGui::IsItemDeactivatedAfterEdit();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("->");
}
if (ImGui::TableNextColumn()) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##dst", "dst", &in.first);
commit |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::PopID();
}
ImGui::PopID();
// ---- output map
ImGui::PushID("output");
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("output");
ImGui::SameLine();
if (ImGui::Button("+")) {
outputs.push_back({"target_output", ""});
commit = true;
}
if (outputs.size() == 0) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextDisabled("no rule");
}
for (size_t i = 0; i < outputs.size(); ++i) {
auto& out = outputs[i];
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
if (ImGui::TableNextColumn()) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##src", "src", &out.first);
commit |= ImGui::IsItemDeactivatedAfterEdit();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("->");
}
if (ImGui::TableNextColumn()) {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputTextWithHint("##dst", "dst", &out.second);
commit |= ImGui::IsItemDeactivatedAfterEdit();
}
ImGui::PopID();
}
ImGui::PopID();
ImGui::EndTable();
}
}
if (commit) {
imm.erase(
std::remove_if(
imm.begin(), imm.end(),
[](auto& x) { return x.first.size() == 0; }),
imm.end());
inputs.erase(
std::remove_if(
inputs.begin(), inputs.end(),
[](auto& x) { return x.first.size() == 0; }),
inputs.end());
outputs.erase(
std::remove_if(
outputs.begin(), outputs.end(),
[](auto& x) { return x.first.size() == 0; }),
outputs.end());
mem_.Commit();
}
ImGui::Spacing();
try {
auto& seq = target_.GetFileOrThrow().interfaceOrThrow<nf7::Sequencer>();
if (seq.flags() & nf7::Sequencer::kParamPanel) {
Adaptor::Editor ed;
seq.UpdateParamPanel(ed);
}
} catch (nf7::Exception&) {
ImGui::Separator();
ImGui::TextUnformatted("TARGET HAS NO SEQUENCER INTERFACE");
}
}
void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept {
ImGui::TextUnformatted("Sequencer/Adaptor");
}
}
} // namespace nf7

255
file/sequencer_call.cc Normal file
View File

@@ -0,0 +1,255 @@
#include "nf7.hh"
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/sequencer.hh"
namespace nf7 {
namespace {
class Call final : public nf7::FileBase, public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Call> kType = {
"Sequencer/Call", {"nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Calls a Node.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class Lambda;
class SessionLambda;
struct Data {
nf7::FileHolder::Tag callee;
std::string expects;
bool pure;
};
Call(nf7::Env& env, Data&& data = {}) noexcept :
FileBase(kType, env, {&callee_, &callee_editor_}),
Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip |
Sequencer::kParamPanel),
life_(*this),
callee_(*this, "callee", mem_),
callee_editor_(callee_,
[](auto& t) { return t.flags().contains("nf7::Node"); }),
mem_(std::move(data), *this) {
mem_.data().callee.SetTarget(callee_);
mem_.CommitAmend();
}
Call(nf7::Deserializer& ar) : Call(ar.env()) {
ar(callee_, data().expects, data().pure);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(callee_, data().expects, data().pure);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Call>(env, Data {data()});
}
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override;
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
void UpdateParamPanel(nf7::Sequencer::Editor&) noexcept override;
void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_);
}
private:
nf7::Life<Call> life_;
nf7::FileHolder callee_;
nf7::gui::FileHolderEditor callee_editor_;
nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); }
};
class Call::Lambda final : public nf7::Sequencer::Lambda,
public std::enable_shared_from_this<Call::Lambda> {
public:
Lambda(Call& f, const std::shared_ptr<nf7::Context>& ctx) noexcept :
Sequencer::Lambda(f, ctx), file_(f.life_) {
}
void Run(const std::shared_ptr<Sequencer::Session>& ss) noexcept override;
void Abort() noexcept override;
private:
nf7::Life<Call>::Ref file_;
std::shared_ptr<Call::SessionLambda> ssla_;
nf7::Node* cached_node_ = nullptr;
std::shared_ptr<Node::Lambda> la_;
bool abort_ = false;
};
class Call::SessionLambda final : public nf7::Node::Lambda {
public:
SessionLambda(Call& f, const std::shared_ptr<Call::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent) {
}
void Listen(Call& f, const std::shared_ptr<Sequencer::Session>& ss) noexcept {
assert(!ss_);
ss_ = ss;
const auto ex = f.data().expects;
size_t begin = 0;
for (size_t i = 0; i <= ex.size(); ++i) {
if (i == ex.size() || ex[i] == '\n') {
auto name = ex.substr(begin, i-begin);
if (name.size() > 0) {
expects_.insert(std::move(name));
}
begin = i+1;
}
}
FinishIf();
}
void Handle(std::string_view name, const nf7::Value& val,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
if (!ss_) return;
ss_->Send(name, nf7::Value {val});
expects_.erase(std::string {name});
FinishIf();
}
void Abort() noexcept override {
if (ss_) {
ss_->Finish();
ss_ = nullptr;
expects_.clear();
}
}
private:
std::shared_ptr<Sequencer::Session> ss_;
std::unordered_set<std::string> expects_;
void FinishIf() noexcept {
if (expects_.size() == 0) {
ss_->Finish();
ss_ = nullptr;
}
}
};
std::shared_ptr<Sequencer::Lambda> Call::CreateLambda(
const std::shared_ptr<nf7::Context>& parent) noexcept {
return std::make_shared<Call::Lambda>(*this, parent);
}
void Call::Lambda::Run(const std::shared_ptr<Sequencer::Session>& ss) noexcept
try {
if (abort_) return;
file_.EnforceAlive();
auto& data = file_->data();
auto& callee = file_->callee_.GetFileOrThrow();
auto& node = callee.interfaceOrThrow<nf7::Node>();
if (!ssla_) {
ssla_ = std::make_shared<Call::SessionLambda>(*file_, shared_from_this());
}
auto self = shared_from_this();
if (!la_ || &node != std::exchange(cached_node_, &node)) {
la_ = node.CreateLambda(ssla_);
}
ssla_->Listen(*file_, ss);
for (const auto& name : node.GetInputs()) {
if (auto v = ss->Receive(name)) {
la_->Handle(name, *v, ssla_);
}
}
if (data.pure) {
ssla_ = nullptr;
la_ = nullptr;
}
} catch (nf7::LifeExpiredException&) {
ss->Finish();
} catch (nf7::FileHolder::EmptyException&) {
ss->Finish();
} catch (nf7::File::NotImplementedException&) {
ss->Finish();
}
void Call::Lambda::Abort() noexcept {
if (ssla_) {
ssla_->Abort();
ssla_ = nullptr;
}
if (la_) {
la_->Abort();
la_ = nullptr;
}
}
void Call::UpdateItem(Sequencer::Editor&) noexcept {
ImGui::Text("%s", callee_editor_.GetDisplayText().c_str());
}
void Call::UpdateParamPanel(Sequencer::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) {
callee_editor_.ButtonWithLabel("callee");
ImGui::InputTextMultiline("expects", &data().expects, {0, 4.f*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("session ends right after receiving these outputs");
}
if (ImGui::Checkbox("pure", &data().pure)) {
mem_.Commit();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("callee's lambda is created for each session");
}
ImGui::Spacing();
callee_editor_.ItemWidget("callee");
}
}
void Call::UpdateTooltip(Sequencer::Editor&) noexcept {
ImGui::TextUnformatted("Sequencer/Call");
}
}
} // namespace nf7

1718
file/sequencer_timeline.cc Normal file

File diff suppressed because it is too large Load Diff

129
file/system_call.cc Normal file
View File

@@ -0,0 +1,129 @@
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <imgui.h>
#include <ImNodes.h>
#include "nf7.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_std_atomic.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Call final : public nf7::File, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Call> kType = {
"System/Call", {"nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Call system features.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda;
Call(nf7::Env& env) noexcept :
nf7::File(kType, env),
nf7::Node(nf7::Node::kCustomNode) {
}
Call(nf7::Deserializer& ar) : Call(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Call>(env);
}
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 {
static const std::vector<std::string> kInputs = {"save", "exit", "abort", "panic"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::Node>(t).Select(this);
}
};
class Call::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Call::Lambda> {
public:
Lambda(Call& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent) {
}
void Handle(std::string_view name, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
if (name == "save") {
env().ExecMain(shared_from_this(), [this]() {
env().Save();
});
} else if (name == "exit") {
env().Exit();
} else if (name == "abort") {
std::abort();
} else if (name == "panic") {
try {
if (v.isString()) {
throw nf7::Exception {v.string()};
} else {
throw nf7::Exception {
"'panic' input can take a string as message shown here :)"};
}
} catch (nf7::Exception&) {
env().Throw(std::make_exception_ptr<nf7::Exception>({"panic caused by System/Call"}));
}
}
}
};
std::shared_ptr<nf7::Node::Lambda> Call::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Call::Lambda>(*this, parent);
}
void Call::UpdateNode(nf7::Node::Editor&) noexcept {
ImGui::TextUnformatted("System/Call");
static const std::vector<std::pair<std::string, std::string>> kSockets = {
{"save", "save entire nf7 system when get any value"},
{"exit", "exit nf7 after saving when get any value"},
{"abort", "[DANGER] abort nf7 process WITHOUT SAVING when get any value"},
{"panic", "take a string message and make a panic to notify user"},
};
for (auto& sock : kSockets) {
if (ImNodes::BeginInputSlot(sock.first.c_str(), 1)) {
nf7::gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(sock.first.c_str());
ImNodes::EndSlot();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(sock.second.c_str());
}
}
}
} // namespace
} // namespace nf7

View File

@@ -8,7 +8,6 @@
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include <yas/types/std/map.hpp>
#include <yas/types/std/unordered_set.hpp>
#include <yas/types/std/string.hpp>
@@ -16,10 +15,12 @@
#include "common/dir.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/gui_dnd.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/gui_window.hh"
#include "common/ptr_selector.hh"
#include "common/yas_nf7.hh"
@@ -28,30 +29,52 @@
namespace nf7 {
namespace {
class Dir final : public File,
class Dir final : public nf7::FileBase,
public nf7::Dir,
public nf7::DirItem {
public:
static inline const GenericTypeInfo<Dir> kType = {"System/Dir", {"DirItem"}};
static inline const GenericTypeInfo<Dir> kType = {"System/Dir", {"nf7::DirItem"}};
static constexpr const char* kTypeDescription = "generic directory";
using ItemMap = std::map<std::string, std::unique_ptr<File>>;
Dir(Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept :
File(kType, env),
DirItem(DirItem::kTree |
DirItem::kMenu |
DirItem::kTooltip |
DirItem::kDragDropTarget),
items_(std::move(items)), win_(*this, "TreeView System/Dir", src) {
Dir(nf7::Env& env, ItemMap&& items = {}, const gui::Window* src = nullptr) noexcept :
nf7::FileBase(kType, env, {&widget_popup_, &add_popup_, &rename_popup_}),
nf7::DirItem(nf7::DirItem::kTree |
nf7::DirItem::kMenu |
nf7::DirItem::kTooltip |
nf7::DirItem::kDragDropTarget),
items_(std::move(items)), win_(*this, "TreeView System/Dir", src),
widget_popup_(*this), add_popup_(*this), rename_popup_(*this) {
}
Dir(Env& env, Deserializer& ar) : Dir(env) {
ar(items_, opened_, win_);
Dir(nf7::Deserializer& ar) : Dir(ar.env()) {
ar(opened_, win_);
uint64_t size;
ar(size);
for (size_t i = 0; i < size; ++i) {
std::string name;
ar(name);
std::unique_ptr<nf7::File> f;
try {
ar(f);
items_[name] = std::move(f);
} catch (nf7::Exception&) {
env().Throw(std::current_exception());
}
}
}
void Serialize(Serializer& ar) const noexcept override {
ar(items_, opened_, win_);
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(opened_, win_);
ar(static_cast<uint64_t>(items_.size()));
for (auto& p : items_) {
ar(p.first, p.second);
}
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
ItemMap items;
for (const auto& item : items_) {
items[item.first] = item.second->Clone(env);
@@ -92,8 +115,13 @@ class Dir final : public File,
void UpdateDragDropTarget() noexcept override;
void Handle(const Event& ev) noexcept override {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case Event::kAdd:
// force to show window if this is the root
if (name() == "$") {
win_.shown() = true;
}
for (const auto& item : items_) item.second->MoveUnder(*this, item.first);
break;
case Event::kRemove:
@@ -113,10 +141,6 @@ class Dir final : public File,
}
private:
const char* popup_ = nullptr;
std::string rename_target_;
// persistent params
ItemMap items_;
gui::Window win_;
@@ -124,6 +148,66 @@ class Dir final : public File,
std::unordered_set<std::string> opened_;
// GUI popup
class WidgetPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
WidgetPopup(Dir& owner) noexcept :
nf7::gui::Popup("WidgetPopup"), owner_(&owner) {
}
void Open(nf7::File& f) noexcept {
target_ = &f;
nf7::gui::Popup::Open();
}
void Update() noexcept override;
private:
Dir* owner_;
nf7::File* target_ = nullptr;
} widget_popup_;
class AddPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
AddPopup(Dir& owner) noexcept :
nf7::gui::Popup("AddPopup"),
owner_(&owner),
factory_(owner, [](auto& t) { return t.flags().contains("nf7::DirItem"); },
nf7::gui::FileFactory::kNameInput |
nf7::gui::FileFactory::kNameDupCheck) {
}
using nf7::gui::Popup::Open;
void Update() noexcept override;
private:
Dir* owner_;
nf7::gui::FileFactory factory_;
} add_popup_;
class RenamePopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
RenamePopup(Dir& owner) noexcept :
nf7::gui::Popup("RenamePopup"),
owner_(&owner) {
}
void Open(std::string_view before) noexcept {
before_ = before;
after_ = "";
nf7::gui::Popup::Open();
}
void Update() noexcept override;
private:
Dir* owner_;
std::string before_;
std::string after_;
} rename_popup_;
std::string GetUniqueName(std::string_view name) const noexcept {
auto ret = std::string {name};
while (Find(ret)) {
@@ -134,6 +218,8 @@ class Dir final : public File,
};
void Dir::Update() noexcept {
nf7::FileBase::Update();
const auto em = ImGui::GetFontSize();
// update children
@@ -143,84 +229,11 @@ void Dir::Update() noexcept {
ImGui::PopID();
}
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
// new item popup
if (ImGui::BeginPopup("NewItemPopup")) {
static nf7::gui::FileCreatePopup<
nf7::gui::kNameInput | nf7::gui::kNameDupCheck> p(
{"File_Factory", "DirItem"});
ImGui::TextUnformatted("System/Dir: adding new file...");
if (p.Update(*this)) {
auto ctx = std::make_shared<nf7::GenericContext>(*this, "adding new item");
auto task = [this, name = p.name(), &type = p.type()]() {
Add(name, type.Create(env()));
};
env().ExecMain(ctx, std::move(task));
}
ImGui::EndPopup();
}
// rename popup
if (ImGui::BeginPopup("RenamePopup")) {
static std::string new_name;
ImGui::TextUnformatted("System/Dir: renaming an exsting item...");
ImGui::InputText("before", &rename_target_);
bool submit = false;
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
if (ImGui::InputText("after", &new_name, ImGuiInputTextFlags_EnterReturnsTrue)) {
submit = true;
}
bool err = false;
if (!Find(rename_target_)) {
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target");
err = true;
}
if (Find(new_name)) {
ImGui::Bullet(); ImGui::TextUnformatted("after is invalid: duplicated name");
err = true;
}
try {
Path::ValidateTerm(new_name);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("after is invalid: %s", e.msg().c_str());
err = true;
}
if (!err) {
if (ImGui::Button("ok")) {
submit = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"rename '%s' to '%s' on '%s'",
rename_target_.c_str(), new_name.c_str(), abspath().Stringify().c_str());
}
}
if (submit) {
ImGui::CloseCurrentPopup();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "renaming item");
auto task = [this, before = std::move(rename_target_), after = std::move(new_name)]() {
auto f = Remove(before);
if (!f) throw Exception("missing target");
Add(after, std::move(f));
};
env().ExecMain(ctx, std::move(task));
}
ImGui::EndPopup();
}
// tree view window
const auto kInit = [em]() {
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({8*em, 8*em}, ImGuiCond_FirstUseEver);
};
if (win_.Begin(kInit)) {
}
if (win_.Begin()) {
if (ImGui::BeginPopupContextWindow()) {
UpdateMenu();
ImGui::EndPopup();
@@ -266,6 +279,12 @@ void Dir::UpdateTree() noexcept {
opened_.erase(name);
}
if (ditem && (ditem->flags() & DirItem::kWidget)) {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
widget_popup_.Open(file);
}
}
// tooltip
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
@@ -282,6 +301,11 @@ void Dir::UpdateTree() noexcept {
// context menu
if (ImGui::BeginPopupContextItem()) {
if (ditem && (ditem->flags() & DirItem::kWidget)) {
if (ImGui::MenuItem("open widget")) {
widget_popup_.Open(file);
}
}
if (ImGui::MenuItem("copy path")) {
ImGui::SetClipboardText(file.abspath().Stringify().c_str());
}
@@ -293,8 +317,7 @@ void Dir::UpdateTree() noexcept {
[this, name]() { Remove(name); });
}
if (ImGui::MenuItem("rename")) {
rename_target_ = name;
popup_ = "RenamePopup";
rename_popup_.Open(name);
}
if (ImGui::MenuItem("renew")) {
@@ -308,7 +331,7 @@ void Dir::UpdateTree() noexcept {
ImGui::Separator();
if (ImGui::MenuItem("add new sibling")) {
popup_ = "NewItemPopup";
add_popup_.Open();
}
if (ditem && (ditem->flags() & DirItem::kMenu)) {
@@ -355,7 +378,7 @@ void Dir::UpdateTree() noexcept {
}
void Dir::UpdateMenu() noexcept {
if (ImGui::MenuItem("add new child")) {
popup_ = "NewItemPopup";
add_popup_.Open();
}
ImGui::Separator();
ImGui::MenuItem("TreeView", nullptr, &win_.shown());
@@ -390,5 +413,83 @@ try {
} catch (nf7::Exception&) {
}
void Dir::WidgetPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
if (auto item = target_->interface<nf7::DirItem>()) {
ImGui::PushID(item);
item->UpdateWidget();
ImGui::PopID();
}
ImGui::EndPopup();
}
}
void Dir::AddPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::TextUnformatted("System/Dir: adding new file...");
if (factory_.Update()) {
ImGui::CloseCurrentPopup();
auto& env = owner_->env();
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "adding new item");
auto task = [this, &env]() { owner_->Add(factory_.name(), factory_.Create(env)); };
env.ExecMain(ctx, std::move(task));
}
ImGui::EndPopup();
}
}
void Dir::RenamePopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::TextUnformatted("System/Dir: renaming an exsting item...");
ImGui::InputText("before", &before_);
bool submit = false;
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
if (ImGui::InputText("after", &after_, ImGuiInputTextFlags_EnterReturnsTrue)) {
submit = true;
}
bool err = false;
if (!owner_->Find(before_)) {
ImGui::Bullet(); ImGui::TextUnformatted("before is invalid: missing target");
err = true;
}
if (owner_->Find(after_)) {
ImGui::Bullet(); ImGui::TextUnformatted("after is invalid: duplicated name");
err = true;
}
try {
Path::ValidateTerm(after_);
} catch (Exception& e) {
ImGui::Bullet(); ImGui::Text("after is invalid: %s", e.msg().c_str());
err = true;
}
if (!err) {
if (ImGui::Button("ok")) {
submit = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"rename '%s' to '%s' on '%s'",
before_.c_str(), after_.c_str(),
owner_->abspath().Stringify().c_str());
}
}
if (submit) {
ImGui::CloseCurrentPopup();
auto ctx = std::make_shared<nf7::GenericContext>(*owner_, "renaming item");
auto task = [this, before = std::move(before_), after = std::move(after_)]() {
auto f = owner_->Remove(before);
if (!f) throw nf7::Exception {"missing target"};
owner_->Add(after, std::move(f));
};
owner_->env().ExecMain(ctx, std::move(task));
}
ImGui::EndPopup();
}
}
}
} // namespace nf7

198
file/system_event.cc Normal file
View File

@@ -0,0 +1,198 @@
#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include <imgui.h>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/gui_file.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/life.hh"
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
namespace nf7 {
namespace {
class Event final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Event> kType = {
"System/Event", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Records log output from other files.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
class Lambda;
struct Data final {
nf7::FileHolder::Tag handler;
};
Event(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&logger_, &handler_, &handler_editor_}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this), logger_(*this),
handler_(*this, "handler", mem_),
handler_editor_(handler_,
[](auto& t) { return t.flags().contains("nf7::Node"); }),
la_root_(std::make_shared<nf7::Node::Lambda>(*this)),
mem_(std::move(data)) {
handler_.onEmplace = [this]() { la_ = nullptr; };
}
Event(nf7::Deserializer& ar) : Event(ar.env()) {
ar(handler_);
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(handler_);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Event>(env, Data {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 {
static const std::vector<std::string> kInputs = {"value"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateWidget() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
nf7::Life<Event> life_;
nf7::LoggerRef logger_;
nf7::FileHolder handler_;
nf7::gui::FileHolderEditor handler_editor_;
std::shared_ptr<nf7::Node::Lambda> la_root_;
std::shared_ptr<nf7::Node::Lambda> la_;
nf7::GenericMemento<Data> mem_;
Data& data() noexcept { return mem_.data(); }
const Data& data() const noexcept { return mem_.data(); }
std::span<const std::string> GetHandlerInputs() noexcept
try {
return handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>().GetInputs();
} catch (nf7::Exception&) {
return {};
}
std::shared_ptr<nf7::Node::Lambda> CreateLambdaIf() noexcept {
try {
if (!la_) {
auto& n = handler_.GetFileOrThrow().interfaceOrThrow<nf7::Node>();
la_ = n.CreateLambda(la_root_);
}
return la_;
} catch (nf7::Exception& e) {
logger_.Warn("failed to create handler's lambda: "+e.msg());
la_ = nullptr;
return nullptr;
}
}
void TriggerKeyEvent(const char* key, const char* type) noexcept {
if (auto la = CreateLambdaIf()) {
la->Handle("key", nf7::Value {std::vector<nf7::Value::TuplePair> {
{"key", std::string {key}},
{"type", std::string {type}},
}}, la_root_);
}
}
void TriggerCustomEvent(const nf7::Value& v) noexcept {
if (auto la = CreateLambdaIf()) {
la->Handle("custom", v, la_root_);
}
}
};
class Event::Lambda final : public nf7::Node::Lambda {
public:
Lambda(Event& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept
try {
f_.EnforceAlive();
f_->TriggerCustomEvent(v);
} catch (nf7::Exception&) {
}
private:
nf7::Life<Event>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> Event::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Event::Lambda>(*this, parent);
}
void Event::Update() noexcept {
nf7::FileBase::Update();
const auto& io = ImGui::GetIO();
const auto in = GetHandlerInputs();
if (in.end() != std::find(in.begin(), in.end(), "key")) {
for (size_t i = 0; i < ImGuiKey_KeysData_SIZE; ++i) {
const auto& key = io.KeysData[i];
const char* event = nullptr;
if (key.DownDuration == 0) {
event = "down";
} else if (key.DownDurationPrev >= 0 && !key.Down) {
event = "up";
}
if (event) {
const auto k = static_cast<ImGuiKey>(i);
TriggerKeyEvent(ImGui::GetKeyName(k), event);
}
}
}
}
void Event::UpdateMenu() noexcept {
if (ImGui::MenuItem("drop handler's lambda")) {
la_ = nullptr;
}
}
void Event::UpdateWidget() noexcept {
ImGui::TextUnformatted("System/Event");
handler_editor_.ButtonWithLabel("handler");
handler_editor_.ItemWidget("handler");
handler_editor_.Update();
}
} // namespace
} // namespace nf7

82
file/system_imgui.cc Normal file
View File

@@ -0,0 +1,82 @@
#include <memory>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <imgui.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/string_view.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/generic_type_info.hh"
#include "common/gui_window.hh"
#include "common/ptr_selector.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class ImGui_ final : public nf7::File, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<ImGui_> kType = {"System/ImGui", {}};
ImGui_(nf7::Env& env) noexcept :
nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kNone) {
}
ImGui_(nf7::Deserializer& ar) : ImGui_(ar.env()) {
std::string config;
ar(config);
if (config.size() > 0) {
ImGui::LoadIniSettingsFromMemory(config.data(), config.size());
}
}
void Serialize(nf7::Serializer& ar) const noexcept override {
size_t n;
const char* config = ImGui::SaveIniSettingsToMemory(&n);
ar(std::string_view(config, n));
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<ImGui_>(env);
}
void Update() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem>(t).Select(this);
}
};
void ImGui_::Update() noexcept {
constexpr auto kFlags =
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoNavFocus;
const auto id = nf7::gui::Window::ConcatId(*this, "Docking Root");
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0, 0});
ImGui::SetNextWindowBgAlpha(0.f);
if (ImGui::Begin(id.c_str(), nullptr, kFlags)) {
const auto vp = ImGui::GetMainViewport();
ImGui::SetWindowPos({0, 0}, ImGuiCond_Always);
ImGui::SetWindowSize(vp->Size, ImGuiCond_Always);
ImGui::DockSpace(ImGui::GetID("DockSpace"), {0, 0},
ImGuiDockNodeFlags_PassthruCentralNode);
}
ImGui::End();
ImGui::PopStyleVar(1);
}
}
} // namespace nf7

View File

@@ -1,67 +0,0 @@
#include <memory>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <imgui.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/string_view.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/generic_type_info.hh"
#include "common/ptr_selector.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class ImGuiConfig final : public File, public nf7::DirItem {
public:
static inline const GenericTypeInfo<ImGuiConfig> kType = {"System/ImGuiConfig", {}};
ImGuiConfig(Env& env) noexcept :
File(kType, env), DirItem(DirItem::kMenu) {
}
ImGuiConfig(Env& env, Deserializer& ar) noexcept : ImGuiConfig(env) {
std::string buf;
ar(buf);
if (buf.empty()) return;
ImGui::LoadIniSettingsFromMemory(buf.data(), buf.size());
}
void Serialize(Serializer& ar) const noexcept override {
if (std::exchange(const_cast<bool&>(skip_save_), false)) {
ar(""s);
} else {
size_t n;
const char* ini = ImGui::SaveIniSettingsToMemory(&n);
ar(std::string_view(ini, n));
}
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<ImGuiConfig>(env);
}
void UpdateMenu() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::DirItem>(t).Select(this);
}
private:
bool skip_save_ = false;
};
void ImGuiConfig::UpdateMenu() noexcept {
ImGui::MenuItem("skip next serialization", nullptr, &skip_save_);
}
}
} // namespace nf7

View File

@@ -1,8 +1,11 @@
#include <atomic>
#include <cinttypes>
#include <deque>
#include <exception>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <iostream>
@@ -10,23 +13,38 @@
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_std_atomic.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Logger final : public nf7::File,
public nf7::DirItem {
public:
static inline const GenericTypeInfo<Logger> kType = {"System/Logger", {"DirItem"}};
static inline const nf7::GenericTypeInfo<Logger> kType = {
"System/Logger", {"nf7::DirItem"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Records log output from other files.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Logger");
ImGui::Bullet(); ImGui::TextUnformatted(
"logged are children and grandchildren of a dir that has this with name '_logger'");
ImGui::Bullet(); ImGui::TextUnformatted(
"recorded logs won't be permanentized");
}
class Node;
struct Row final {
public:
@@ -37,6 +55,7 @@ class Logger final : public nf7::File,
std::string msg;
std::string path;
std::string location;
std::exception_ptr ex;
std::string Stringify() const noexcept {
std::stringstream st;
@@ -57,34 +76,34 @@ class Logger final : public nf7::File,
};
class ItemStore;
Logger(Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept :
Logger(nf7::Env& env, uint32_t max_rows = 1024, bool propagate = false, bool freeze = false) noexcept :
File(kType, env), DirItem(DirItem::kMenu),
param_(std::make_shared<Param>(max_rows, propagate, freeze)),
win_(*this, "LogView") {
win_.shown() = true;
}
Logger(Env& env, Deserializer& ar) : Logger(env) {
Logger(nf7::Deserializer& ar) : Logger(ar.env()) {
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
if (param_->max_rows == 0) {
throw DeserializeException("max_rows must be 1 or more");
}
}
void Serialize(Serializer& ar) const noexcept override {
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, param_->max_rows, param_->propagate, param_->freeze);
}
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<Logger>(
env, param_->max_rows, param_->propagate, param_->freeze);
}
void Handle(const Event& ev) noexcept override;
void Handle(const nf7::File::Event& ev) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateRowMenu(const Row&) noexcept;
File::Interface* interface(const std::type_info& t) noexcept override {
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::DirItem, nf7::Logger>(t).
Select(this, store_.get());
}
@@ -96,7 +115,7 @@ class Logger final : public nf7::File,
const char* popup_ = nullptr;
gui::Window win_;
nf7::gui::Window win_;
void DropExceededRows() noexcept {
@@ -126,10 +145,9 @@ class Logger final : public nf7::File,
}
}
static std::string GetLocationString(const std::source_location loc) noexcept {
return loc.file_name()+":"s+loc.function_name()+":"s+std::to_string(loc.line());
return loc.file_name()+":"s+std::to_string(loc.line());
}
};
class Logger::ItemStore final : public nf7::Context,
public nf7::Logger,
public std::enable_shared_from_this<ItemStore> {
@@ -176,6 +194,7 @@ class Logger::ItemStore final : public nf7::Context,
.msg = std::move(itr->msg),
.path = owner.GetPathString(itr->file),
.location = GetLocationString(itr->srcloc),
.ex = itr->ex,
};
rows.push_back(std::move(row));
}
@@ -197,6 +216,76 @@ class Logger::ItemStore final : public nf7::Context,
std::shared_ptr<Param> param_;
};
class Logger::Node final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Logger::Node> kType = {
"System/Logger/Node", {"nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Sends message to logger.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
}
Node(nf7::Env& env) noexcept :
nf7::FileBase(kType, env, {&logger_}),
nf7::Node(nf7::Node::kNone),
life_(*this), logger_(*this) {
}
Node(nf7::Deserializer& ar) : Node(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Logger::Node>(env);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
return std::make_shared<Logger::Node::Lambda>(*this, parent);
}
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"msg"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return {};
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::Node>(t).Select(this);
}
private:
nf7::Life<Logger::Node> life_;
nf7::LoggerRef logger_;
class Lambda final : public nf7::Node::Lambda {
public:
Lambda(Logger::Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override
try {
f_.EnforceAlive();
if (v.isString()) {
f_->logger_.Info(v.string());
} else {
f_->logger_.Info("["s+v.typeName()+"]");
}
} catch (nf7::Exception&) {
}
private:
nf7::Life<Logger::Node>::Ref f_;
};
};
void Logger::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
@@ -209,7 +298,6 @@ void Logger::Handle(const Event& ev) noexcept {
return;
}
}
void Logger::Update() noexcept {
if (const auto name = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(name);
@@ -251,10 +339,10 @@ void Logger::Update() noexcept {
}
// LogView
const auto kInit = [em]() {
if (win_.shownInCurrentFrame()) {
ImGui::SetNextWindowSize({48*em, 16*em}, ImGuiCond_FirstUseEver);
};
if (win_.Begin(kInit)) {
}
if (win_.Begin()) {
constexpr auto kTableFlags =
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Hideable |
@@ -312,10 +400,19 @@ void Logger::Update() noexcept {
ImGui::TextUnformatted(row.location.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("file: %s", row.srcloc.file_name());
ImGui::Text("func: %s", row.srcloc.function_name());
ImGui::Text("line: %zu", static_cast<size_t>(row.srcloc.line()));
ImGui::Text("col : %zu", static_cast<size_t>(row.srcloc.column()));
ImGui::Text(row.location.c_str());
for (auto ptr = row.ex; ptr;)
try {
ImGui::Bullet();
std::rethrow_exception(ptr);
} catch (Exception& e) {
e.UpdatePanic();
ImGui::Spacing();
ptr = e.reason();
} catch (std::exception& e) {
ImGui::Text("std::exception (%s)", e.what());
ptr = nullptr;
}
ImGui::EndTooltip();
}
}
@@ -337,6 +434,11 @@ void Logger::UpdateRowMenu(const Row& row) noexcept {
if (ImGui::MenuItem("copy as text")) {
ImGui::SetClipboardText(row.Stringify().c_str());
}
ImGui::Separator();
if (ImGui::MenuItem("clear")) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this), [this]() { rows_.clear(); });
}
}
}

View File

@@ -1,6 +1,5 @@
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <string>
@@ -12,160 +11,348 @@
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include <yas/types/std/chrono.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/async_buffer_adaptor.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_popup.hh"
#include "common/gui_window.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/native_file.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/queue.hh"
#include "common/thread.hh"
#include "common/yas_std_filesystem.hh"
namespace nf7 {
namespace {
class NativeFile final : public File,
public nf7::DirItem {
class NativeFile final : public nf7::FileBase,
public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<NativeFile> kType = {
"System/NativeFile", {"AsyncBuffer", "DirItem"}};
NativeFile(Env& env, const std::filesystem::path& path = "", std::string_view mode = "") noexcept :
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
npath_(path), mode_(mode) {
static inline const nf7::GenericTypeInfo<NativeFile> kType = {
"System/NativeFile", {"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");
}
NativeFile(Env& env, Deserializer& ar) : NativeFile(env) {
ar(npath_, mode_, lastmod_);
class Lambda;
struct SharedData final {
SharedData(NativeFile& f) noexcept : log(f) {
}
nf7::LoggerRef log;
std::optional<nf7::NativeFile> nfile;
std::atomic<bool> locked = false;
};
struct Runner final {
struct Task {
std::shared_ptr<NativeFile::Lambda> callee;
std::shared_ptr<nf7::Node::Lambda> 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;
private:
std::shared_ptr<SharedData> shared_;
};
using Thread = nf7::Thread<Runner, Runner::Task>;
struct Data final {
std::filesystem::path npath;
std::string mode;
};
NativeFile(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&config_popup_}),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip |
nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kMenu_DirItem),
life_(*this),
shared_(std::make_shared<SharedData>(*this)),
th_(std::make_shared<Thread>(*this, Runner {shared_})),
mem_(std::move(data), *this),
config_popup_(*this) {
nf7::FileBase::Install(shared_->log);
mem_.onRestore = [this]() { Refresh(); };
mem_.onCommit = [this]() { Refresh(); };
}
void Serialize(Serializer& ar) const noexcept override {
ar(npath_, mode_, lastmod_);
NativeFile(nf7::Deserializer& ar) : NativeFile(ar.env()) {
ar(data().npath, data().mode);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<NativeFile>(env, npath_, 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()});
}
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 {
static const std::vector<std::string> kInputs = {"command"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"result"};
return kOutputs;
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void Handle(const Event& ev) noexcept override {
switch (ev.type) {
case Event::kAdd:
Reset();
return;
case Event::kRemove:
buf_ = nullptr;
return;
default:
return;
}
}
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::AsyncBuffer, nf7::DirItem>(t).Select(this, buf_.get());
return InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
std::shared_ptr<nf7::AsyncBufferAdaptor> buf_;
nf7::Life<NativeFile> life_;
const char* popup_ = nullptr;
std::shared_ptr<SharedData> shared_;
std::shared_ptr<Thread> th_;
// persistent params
std::filesystem::path npath_;
std::string mode_;
std::filesystem::file_time_type lastmod_;
nf7::GenericMemento<Data> mem_;
void Reset() noexcept {
bool exlock = false;
nf7::Buffer::Flags flags = 0;
for (auto c : mode_) {
if (c == 'x') exlock = true;
flags |=
c == 'r'? nf7::Buffer::kRead:
c == 'w'? nf7::Buffer::kWrite: 0;
const Data& data() const noexcept { return mem_.data(); }
Data& data() noexcept { return mem_.data(); }
// GUI popup
struct ConfigPopup final :
public nf7::FileBase::Feature, private nf7::gui::Popup {
public:
ConfigPopup(NativeFile& f) noexcept :
nf7::gui::Popup("ConfigPopup"), f_(&f) {
}
auto buf = std::make_shared<
nf7::NativeFile>(*this, env().npath()/npath_, flags, exlock);
buf_ = std::make_shared<nf7::AsyncBufferAdaptor>(buf, buf);
void Open() noexcept {
npath_ = f_->data().npath.generic_string();
const auto& mode = f_->data().mode;
read_ = std::string::npos != mode.find('r');
write_ = std::string::npos != mode.find('w');
nf7::gui::Popup::Open();
}
void Update() noexcept override;
private:
NativeFile* 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));
}
};
class NativeFile::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<NativeFile::Lambda> {
public:
Lambda(NativeFile& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), shared_(f.shared_) {
}
~Lambda() noexcept {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
f_.EnforceAlive();
const auto type = v.tuple("type").string();
if (type == "lock") {
Push(caller, [this]() {
Lock();
return nf7::Value::Pulse {};
});
} else if (type == "unlock") {
Push(caller, [this]() {
shared_->nfile = std::nullopt;
Unlock();
return nf7::Value::Pulse {};
});
} 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]() {
std::vector<uint8_t> buf;
buf.resize(size);
const auto actual = shared_->nfile->Read(offset, buf.data(), size);
buf.resize(actual);
return nf7::Value {std::move(buf)};
});
} 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]() {
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]() {
shared_->nfile->Truncate(size);
return nf7::Value::Pulse {};
});
} else {
throw nf7::Exception {"unknown command type: "+type};
}
} catch (nf7::Exception& e) {
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_;
std::shared_ptr<SharedData> shared_;
bool own_lock_ = false;
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;
auto self = shared_from_this();
f_->th_->Push(self, NativeFile::Runner::Task {
.callee = self,
.caller = caller,
.func = std::move(f),
.npath = f_->data().npath,
.flags = flags,
.preproc = {},
});
}
};
std::shared_ptr<nf7::Node::Lambda> NativeFile::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());
}
void NativeFile::Update() noexcept {
nf7::FileBase::Update();
// file update check
try {
const auto lastmod = std::filesystem::last_write_time(env().npath()/npath_);
const auto npath = env().npath() / data().npath;
const auto lastmod = std::filesystem::last_write_time(npath);
if (std::exchange(lastmod_, lastmod) < lastmod) {
Touch();
}
} catch (std::filesystem::filesystem_error&) {
}
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
void NativeFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
config_popup_.Open();
}
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string path;
static bool flag_exlock;
static bool flag_readable;
static bool flag_writeable;
}
void NativeFile::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");
ImGui::TextUnformatted("System/NativeFile: config");
if (ImGui::IsWindowAppearing()) {
path = npath_.generic_string();
flag_exlock = mode_.find('x') != std::string::npos;
flag_readable = mode_.find('r') != std::string::npos;
flag_writeable = mode_.find('w') != std::string::npos;
}
ImGui::InputText("path", &path);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"path to the native file system (base: '%s')",
env().npath().generic_string().c_str());
}
ImGui::Checkbox("exclusive lock", &flag_exlock);
ImGui::Checkbox("readable", &flag_readable);
ImGui::Checkbox("writeable", &flag_writeable);
if (ImGui::Button("config")) {
config_popup_.Open();
}
config_popup_.Update();
}
void NativeFile::ConfigPopup::Update() noexcept {
if (nf7::gui::Popup::Begin()) {
ImGui::InputText("path", &npath_);
ImGui::Checkbox("read", &read_);
ImGui::Checkbox("write", &write_);
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
npath_ = path;
mode_ = "";
if (flag_exlock) mode_ += 'x';
if (flag_readable) mode_ += 'r';
if (flag_writeable) mode_ += 'w';
auto& d = f_->data();
d.npath = npath_;
auto ctx = std::make_shared<nf7::GenericContext>(*this, "resetting native file handle");
env().ExecMain(ctx, [this]() { Reset(); Touch(); });
d.mode = "";
if (read_) d.mode += "r";
if (write_) d.mode += "w";
f_->mem_.Commit();
}
if (!std::filesystem::exists(env().npath()/path)) {
ImGui::Bullet(); ImGui::TextUnformatted("target file seems to be missing...");
if (!std::filesystem::exists(f_->env().npath()/npath_)) {
ImGui::Bullet();
ImGui::TextUnformatted("file not found");
}
ImGui::EndPopup();
}
}
void NativeFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
}
void NativeFile::UpdateTooltip() noexcept {
ImGui::Text("basepath: %s", env().npath().generic_string().c_str());
ImGui::Text("path : %s", npath_.generic_string().c_str());
ImGui::Text("mode : %s", mode_.c_str());
}
}
} // namespace nf7

582
file/value_curve.cc Normal file
View File

@@ -0,0 +1,582 @@
#include <cmath>
#include <limits>
#include <memory>
#include <string_view>
#include <utility>
#include <imgui.h>
#include <imgui_internal.h>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_node.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/sequencer.hh"
#include "common/value.hh"
#include "common/yas_imgui.hh"
namespace nf7 {
namespace {
class Curve final : public nf7::File,
public nf7::DirItem,
public nf7::Node,
public nf7::Sequencer {
public:
static inline const nf7::GenericTypeInfo<Curve> kType =
{"Value/Curve", {"nf7::DirItem", "nf7::Node", "nf7::Sequencer"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("bezier curve");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Sequencer");
ImGui::Bullet(); ImGui::TextUnformatted(
"changes will be applied to active lambdas immediately");
}
class NodeLambda;
class SeqLambda;
struct Term {
ImVec2 p1, p2, p3; // p4 is next point's p1
uint64_t id; // id is not saved
bool break_prev = false;
void serialize(auto& ar) {
ar(p1, p2, p3, break_prev);
}
};
struct Data {
std::vector<Term> terms = {
{.p1 = {0, 0}, .p2 = {0, 0}, .p3 = {1, 1}, .id = 0,},
{.p1 = {1, 1}, .p2 = {1, 1}, .p3 = {1, 1}, .id = 0,}};
Data() {}
void serialize(auto& ar) {
ar(terms);
}
};
Curve(nf7::Env& env, Data&& data = {}) noexcept :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
nf7::Sequencer(nf7::Sequencer::kCustomItem |
nf7::Sequencer::kParamPanel),
life_(*this), mem_(std::move(data), *this) {
AssignId();
Sanitize();
}
Curve(nf7::Deserializer& ar) : Curve(ar.env()) {
ar(mem_.data());
AssignId();
Sanitize();
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Curve>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::shared_ptr<nf7::Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"x"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"y"};
return kOutputs;
}
void UpdateItem(nf7::Sequencer::Editor&) noexcept override;
void UpdateParamPanel(nf7::Sequencer::Editor&) noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
void UpdateCurveEditorWindow(const ImVec2&) noexcept;
void UpdateCurveEditor(const ImVec2&) noexcept;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node, nf7::Sequencer>(t).Select(this, &mem_);
}
private:
nf7::Life<Curve> life_;
uint64_t next_id_ = 1;
nf7::GenericMemento<Data> mem_;
// GUI parameters
std::unordered_set<uint64_t> selected_;
bool last_action_moved_ = false;
void AddPoint(const ImVec2& pos) noexcept {
auto& terms = mem_.data().terms;
const auto x = std::clamp(pos.x, 0.f, 1.f);
auto itr = std::find_if(terms.begin(), terms.end(),
[x](auto& a) { return x <= a.p1.x; });
assert(itr != terms.end());
if (itr == terms.begin()) {
++itr;
}
auto& pt = *(itr-1);
auto nt = itr+1 < terms.end()? &*(itr+1): nullptr;
auto p3 = pt.p3;
pt.p3 = pos;
pt.p2.x = std::clamp(pt.p2.x, pt.p1.x, pos.x);
p3.x = std::clamp(p3.x, pos.x, nt? nt->p1.x: 1.f);
terms.insert(itr, {.p1 = pos, .p2 = pos, .p3 = p3, .id = next_id_++});
}
void RemoveSelectedPoints() noexcept {
auto& terms = mem_.data().terms;
assert(terms.size() >= 2);
terms.erase(
std::remove_if(terms.begin()+1, terms.end()-1,
[&](auto& x) { return selected_.contains(x.id); }),
terms.end()-1);
selected_.clear();
}
void ResetControlsOfSelectedPoints() noexcept {
auto& terms = mem_.data().terms;
for (auto id : selected_) {
auto itr = std::find_if(terms.begin(), terms.end(),
[id](auto& x) { return x.id == id; });
if (itr == terms.end()) {
continue;
}
auto& t = *itr;
auto pt = itr > terms.begin()? &*(itr-1): nullptr;
if (pt) {
pt->p3 = t.p1;
}
t.p2 = t.p1;
}
}
void MovePoint(ImVec2 diff) noexcept {
auto& terms = mem_.data().terms;
for (auto id : selected_) {
auto itr = std::find_if(terms.begin(), terms.end(),
[id](auto& x) { return x.id == id; });
if (itr == terms.end()) {
continue;
}
auto& t = *itr;
auto pt = itr > terms.begin()? &*(itr-1): nullptr;
auto nt = itr+1 < terms.end()? &*(itr+1): nullptr;
const auto pp1 = t.p1;
t.p1 += diff;
t.p1.x = std::clamp(t.p1.x, 0.f, 1.f);
t.p1.y = std::clamp(t.p1.y, 0.f, 1.f);
if (!pt) {
t.p1.x = 0;
} else if (!nt) {
t.p1.x = 1;
}
const auto adiff = t.p1 - pp1;
t.p2 += adiff;
t.p2.x = std::clamp(t.p2.x, t.p1.x, nt? nt->p1.x: 0);
t.p2.y = std::clamp(t.p2.y, 0.f, 1.f);
t.p3.x = std::clamp(t.p3.x, t.p1.x, nt? nt->p1.x: 0);
if (pt) {
pt->p3 += adiff;
pt->p3.x = std::clamp(pt->p3.x, pt->p1.x, t.p1.x);
pt->p3.y = std::clamp(pt->p3.y, 0.f, 1.f);
pt->p2.x = std::clamp(pt->p2.x, pt->p1.x, t.p1.x);
}
}
}
void SelectPoint(uint64_t id, bool single = !ImGui::GetIO().KeyCtrl) noexcept {
if (single) {
selected_.clear();
}
selected_.insert(id);
}
void AssignId() noexcept {
for (auto& term : mem_.data().terms) {
term.id = next_id_++;
}
}
void Sanitize() noexcept {
auto& terms = mem_.data().terms;
std::sort(terms.begin(), terms.end(),
[](auto& a, auto& b) {
return
a.p1.x < b.p1.x? true: a.p1.x == b.p1.x? a.id < b.id: false;
});
for (auto itr = terms.begin(); itr+1 < terms.end(); ++itr) {
auto& a = *itr;
auto& b = *(itr+1);
a.p2.x = std::clamp(a.p2.x, a.p1.x, b.p1.x);
a.p3.x = std::clamp(a.p3.x, a.p1.x, b.p1.x);
}
}
double Calc(double x) const noexcept {
const auto& terms = mem_.data().terms;
assert(terms.size() >= 2);
x = std::clamp(x, 0., 1.);
auto r_itr = std::find_if(terms.begin(), terms.end(),
[x](auto& a) { return x <= a.p1.x; });
assert(r_itr != terms.end());
if (r_itr == terms.begin()) {
return static_cast<double>(r_itr->p1.y);
}
auto l_itr = r_itr-1;
const auto lx = static_cast<double>(l_itr->p1.x);
const auto rx = static_cast<double>(r_itr->p1.x);
const auto xlen = rx-lx;
if (xlen == 0) {
return l_itr->p1.y;
}
const auto ly = static_cast<double>(l_itr->p1.y);
const auto ry = static_cast<double>(r_itr->p1.y);
const auto ylen = ry-ly;
const auto xf = (x-lx)/xlen;
const auto x1 = (static_cast<double>(l_itr->p2.x)-lx)/xlen;
const auto y1 = (static_cast<double>(l_itr->p2.y)-ly)/ylen;
const auto x2 = (static_cast<double>(l_itr->p3.x)-lx)/xlen;
const auto y2 = (static_cast<double>(l_itr->p3.y)-ly)/ylen;
const auto b = Bezier(xf, x1, y1, x2, y2);
return b*ylen + ly;
}
static double Bezier(double x, double x1, double y1, double x2, double y2) noexcept {
double a = 0.5;
double t = 0.5;
for (;;) {
const auto rt = 1-t;
const auto xt = 3*t*rt*rt*x1 + 3*t*t*rt*x2 + t*t*t;
const auto diff = std::abs(xt - x);
if (diff < 1e-2) {
break;
}
a /= 2;
if (xt > x) {
t -= a;
} else if (xt < x) {
t += a;
}
}
const auto rt = 1-t;
return 3*t*rt*rt*y1 + 3*t*t*rt*y2 + t*t*t;
}
};
class Curve::NodeLambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Curve::NodeLambda> {
public:
NodeLambda(Curve& f, const std::shared_ptr<Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(std::string_view, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
try {
f_.EnforceAlive();
caller->Handle("y", f_->Calc(v.scalar()), shared_from_this());
} catch (nf7::Exception&) {
}
private:
nf7::Life<Curve>::Ref f_;
};
std::shared_ptr<nf7::Node::Lambda> Curve::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Curve::NodeLambda>(*this, parent);
}
class Curve::SeqLambda final : public nf7::Sequencer::Lambda {
public:
SeqLambda(Curve& f, const std::shared_ptr<nf7::Context>& parent) noexcept :
nf7::Sequencer::Lambda(f, parent), f_(f.life_) {
}
void Run(const std::shared_ptr<nf7::Sequencer::Session>& ss) noexcept {
try {
ss->Send("y", nf7::Value {f_->Calc(ss->ReceiveOrThrow("x").scalar())});
} catch (nf7::Exception&) {
}
ss->Finish();
}
private:
nf7::Life<Curve>::Ref f_;
};
std::shared_ptr<nf7::Sequencer::Lambda> Curve::CreateLambda(
const std::shared_ptr<nf7::Context>& parent) noexcept {
return std::make_shared<Curve::SeqLambda>(*this, parent);
}
void Curve::UpdateItem(nf7::Sequencer::Editor&) noexcept {
ImGui::TextUnformatted("Value/Curve");
const auto pad = ImGui::GetStyle().WindowPadding / 2;
ImGui::SetCursorPos(pad);
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad);
}
void Curve::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("Value/Curve");
if (ImNodes::BeginInputSlot("x", 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
ImGui::SameLine();
UpdateCurveEditorWindow({16*em, 6*em});
ImGui::SameLine();
if (ImNodes::BeginOutputSlot("y", 1)) {
ImGui::AlignTextToFramePadding();
nf7::gui::NodeSocket();
ImNodes::EndSlot();
}
}
void Curve::UpdateParamPanel(nf7::Sequencer::Editor&) noexcept {
if (ImGui::CollapsingHeader("Value/Curve", ImGuiTreeNodeFlags_DefaultOpen)) {
const auto em = ImGui::GetFontSize();
UpdateCurveEditorWindow({0, 6*em});
}
}
void Curve::UpdateWidget() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("Value/Curve");
UpdateCurveEditorWindow({24*em, 8*em});
}
void Curve::UpdateCurveEditorWindow(const ImVec2& size) noexcept {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0, 0});
const auto shown =
ImGui::BeginChild("CurveEditor", size, true, ImGuiWindowFlags_NoScrollbar);
ImGui::PopStyleVar(1);
if (shown) {
const auto pad = ImGui::GetStyle().WindowPadding / 2;
ImGui::SetCursorPos(pad);
UpdateCurveEditor(ImGui::GetContentRegionAvail()-pad*2);
ImGui::EndChild();
}
}
void Curve::UpdateCurveEditor(const ImVec2& sz) noexcept {
const auto& io = ImGui::GetIO();
auto d = ImGui::GetWindowDrawList();
const auto em = ImGui::GetFontSize();
const auto col = ImGui::GetColorU32(ImGuiCol_Text);
const auto colg = ImGui::GetColorU32(ImGuiCol_Text, .5f);
const auto cols = ImGui::GetColorU32(ImGuiCol_TextSelectedBg);
const auto pos = ImGui::GetCursorScreenPos();
const auto pad = ImGui::GetCursorPos();
const auto grip = em/2.4f;
const auto mpos = ImGui::GetMousePos() - pos;
const auto mposn = ImVec2 {
std::clamp(mpos.x/sz.x, 0.f, 1.f),
std::clamp(1-mpos.y/sz.y, 0.f, 1.f),
};
// draw lines
auto& terms = mem_.data().terms;
for (size_t i = 0; i+1 < terms.size(); ++i) {
const auto& a = terms[i];
const auto& b = terms[i+1];
const auto p1 = ImVec2 {sz.x*a.p1.x, sz.y*(1-a.p1.y)};
const auto p2 = ImVec2 {sz.x*a.p2.x, sz.y*(1-a.p2.y)};
const auto p3 = ImVec2 {sz.x*a.p3.x, sz.y*(1-a.p3.y)};
const auto p4 = ImVec2 {sz.x*b.p1.x, sz.y*(1-b.p1.y)};
d->AddBezierCubic(pos+p1, pos+p2, pos+p3, pos+p4, col, 1);
}
// draw points
bool request_sort = false;
bool skip_adding = false;
bool remove_selected = false;
bool reset_controls = false;
for (size_t i = 0; i < terms.size(); ++i) {
auto& t = terms[i];
auto pt = i >= 1? &terms[i-1]: nullptr;
auto nt = i+1 < terms.size()? &terms[i+1]: nullptr;
const bool sel = selected_.contains(t.id);
ImGui::PushID(static_cast<int>(t.id));
const auto x = std::clamp(sz.x*t.p1.x, 1.f, sz.x-1);
const auto y = std::clamp(sz.y*(1-t.p1.y), 1.f, sz.y-1);
const auto p1 = ImVec2 {x, y};
d->AddCircleFilled(pos+p1, grip, col);
if (sel) {
d->AddCircleFilled(pos+p1, grip, cols);
}
ImGui::SetCursorPos(p1 - ImVec2 {grip, grip} + pad);
if (!io.KeyShift) {
ImGui::InvisibleButton("grip", {grip*2, grip*2});
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
SelectPoint(t.id);
last_action_moved_ = false;
}
request_sort = true;
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
MovePoint(mposn-t.p1);
last_action_moved_ = true;
}
} else {
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
if (ImGui::IsItemHovered()) {
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
}
if (ImGui::BeginPopupContextItem()) {
if (ImGui::IsWindowAppearing()) {
SelectPoint(t.id);
}
if (ImGui::MenuItem("remove points")) {
remove_selected = true;
}
if (ImGui::MenuItem("reset control points")) {
reset_controls = true;
}
ImGui::EndPopup();
}
}
// define control point handler
const auto HandleControlPoint = [&](ImVec2& p, float xmin, float xmax) {
bool ret = false;
if (ImGui::IsItemActive()) {
if (ImGui::IsItemActivated()) {
last_action_moved_ = false;
}
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
p = mposn;
p.x = std::clamp(p.x, xmin, xmax);
p.y = std::clamp(p.y, 0.f, 1.f);
last_action_moved_ = true;
}
ret = true;
} else {
if (ImGui::IsItemHovered()) {
skip_adding = true;
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
}
return ret;
};
// p2 control point
if (ImGui::IsWindowFocused() && io.KeyShift && nt) {
const auto p2 = ImVec2 {sz.x*t.p2.x, sz.y*(1-t.p2.y)};
ImGui::SetCursorPos(p2 - ImVec2 {grip, grip} + pad);
ImGui::InvisibleButton("grip-p2", {grip*2, grip*2});
if (HandleControlPoint(t.p2, t.p1.x, nt->p1.x)) {
if (!t.break_prev) {
// TODO calc reversal vector
}
}
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
d->AddLine(p1+pos, p2+pos, colg);
d->AddCircleFilled(pos+p2, grip, colg);
}
// prev term's p3 control point
if (ImGui::IsWindowFocused() && io.KeyShift && pt) {
const auto p3 = ImVec2 {sz.x*pt->p3.x, sz.y*(1-pt->p3.y)};
ImGui::SetCursorPos(p3 - ImVec2 {grip, grip} + pad);
ImGui::InvisibleButton("grip-p3", {grip*2, grip*2});
if (HandleControlPoint(pt->p3, pt->p1.x, t.p1.x)) {
if (!t.break_prev && nt) {
// TODO calc reversal vector
}
}
if (ImGui::IsItemDeactivated() && last_action_moved_) {
mem_.Commit();
}
d->AddLine(p1+pos, p3+pos, colg);
d->AddCircleFilled(pos+p3, grip, colg);
}
ImGui::PopID();
}
if (request_sort) {
Sanitize();
}
if (remove_selected) {
RemoveSelectedPoints();
}
if (reset_controls) {
ResetControlsOfSelectedPoints();
}
// add new point
if (!skip_adding) {
ImGui::PushID(static_cast<int>(next_id_));
const auto y = static_cast<float>(Calc(mposn.x));
const auto diff = y - mposn.y;
if (std::abs(diff * sz.y) < grip) {
ImGui::SetCursorPos(mpos-ImVec2 {grip/2, grip/2} + pad);
ImGui::InvisibleButton("grip", {grip, grip});
if (ImGui::IsItemActivated()) {
SelectPoint(next_id_);
AddPoint({mposn.x, y});
}
d->AddCircle(ImVec2 {mpos.x, sz.y*(1-y)} + pos, grip, col);
}
ImGui::PopID();
}
}
}
} // namespace nf7

19
init.hh Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "nf7.hh"
#include "common/dir.hh"
inline std::unique_ptr<nf7::File> CreateRoot(nf7::Env& env) noexcept {
auto ret = nf7::File::registry("System/Dir").Create(env);
auto& dir = ret->interfaceOrThrow<nf7::Dir>();
dir.Add("_audio", nf7::File::registry("Audio/Context").Create(env));
dir.Add("_imgui", nf7::File::registry("System/ImGui").Create(env));
dir.Add("_logger", nf7::File::registry("System/Logger").Create(env));
dir.Add("_luajit", nf7::File::registry("LuaJIT/Context").Create(env));
dir.Add("home", nf7::File::registry("System/Dir").Create(env));
return ret;
}

86
main.cc
View File

@@ -18,28 +18,25 @@
#include "nf7.hh"
#include "common/queue.hh"
#include "common/wait_queue.hh"
#include "common/timed_queue.hh"
#include "common/yas_nf7.hh"
// Include glfw lastly to prevent conflict with windows.h.
#include <GLFW/glfw3.h>
#include "init.hh"
#include "theme.hh"
using namespace nf7;
using namespace std::literals;
class Env final : public nf7::Env {
public:
static constexpr auto kFileName = "root.nf7";
static constexpr char kDefaultRoot[] = {
# include "generated/root.nf7.inc"
};
static constexpr auto kFileName = "root.nf7";
static constexpr size_t kSubTaskUnit = 64;
Env() noexcept : nf7::Env(std::filesystem::current_path()) {
::Env::Push(*this);
// start threads
main_thread_ = std::thread([this]() { MainThread(); });
async_threads_.resize(std::max<size_t>(std::thread::hardware_concurrency(), 2));
@@ -48,22 +45,16 @@ class Env final : public nf7::Env {
}
// deserialize
try {
if (!std::filesystem::exists(kFileName)) {
std::ofstream of(kFileName, std::ios::binary);
if (!of) throw Exception("failed to open native file: "s+kFileName);
of.write(kDefaultRoot, sizeof(kDefaultRoot));
of.flush();
if (!of) throw Exception("failed to write to native file: "s+kFileName);
}
if (!std::filesystem::exists(kFileName)) {
root_ = CreateRoot(*this);
root_->MakeAsRoot();
} else {
try {
yas::load<yas::file|yas::binary>("root.nf7", root_);
nf7::Deserializer::Load(*this, kFileName, root_);
root_->MakeAsRoot();
} catch (yas::io_exception&) {
throw Exception("failed to read: "s+kFileName);
} catch (nf7::Exception&) {
Panic();
}
} catch (Exception&) {
Panic();
}
}
~Env() noexcept {
@@ -81,8 +72,6 @@ class Env final : public nf7::Env {
async_.Notify();
for (auto& th : async_threads_) th.join();
::Env::Pop();
}
void ExecMain(const std::shared_ptr<Context>& ctx, Task&& task) noexcept override {
@@ -91,8 +80,8 @@ class Env final : public nf7::Env {
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) noexcept override {
async_.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
@@ -111,10 +100,18 @@ class Env final : public nf7::Env {
} catch (ExpiredException&) {
}
void Exit() noexcept override {
exit_requested_ = true;
}
void Save() noexcept override {
yas::file_ostream os(kFileName, yas::file_trunc);
yas::binary_oarchive<yas::file_ostream, yas::binary> oa(os);
oa & root_;
try {
nf7::Serializer::Save(kFileName, root_);
} catch (nf7::Exception&) {
Panic();
}
}
void Throw(std::exception_ptr&& eptr) noexcept override {
Panic(std::move(eptr));
}
void Update() noexcept {
@@ -136,6 +133,8 @@ class Env final : public nf7::Env {
cv_.notify_one();
}
bool exitRequested() const noexcept { return exit_requested_; }
protected:
File* GetFile(File::Id id) const noexcept override {
auto itr = files_.find(id);
@@ -150,13 +149,6 @@ class Env final : public nf7::Env {
files_.erase(id);
}
void AddContext(Context& ctx) noexcept override {
ctxs_.push_back(&ctx);
}
void RemoveContext(Context& ctx) noexcept override {
ctxs_.erase(std::remove(ctxs_.begin(), ctxs_.end(), &ctx), ctxs_.end());
}
void AddWatcher(File::Id id, Watcher& w) noexcept override {
watchers_map_[id].push_back(&w);
watchers_rmap_[&w].push_back(id);
@@ -172,21 +164,20 @@ 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::vector<Context*> ctxs_;
std::unordered_map<File::Id, std::vector<Watcher*>> watchers_map_;
std::unordered_map<Watcher*, std::vector<File::Id>> watchers_rmap_;
using TaskItem = std::pair<std::shared_ptr<nf7::Context>, Task>;
Queue<TaskItem> main_;
Queue<TaskItem> sub_;
WaitQueue<TaskItem> async_;
nf7::Queue<TaskItem> main_;
nf7::Queue<TaskItem> sub_;
nf7::TimedWaitQueue<TaskItem> async_;
std::mutex mtx_;
std::condition_variable cv_;
@@ -201,7 +192,7 @@ class Env final : public nf7::Env {
void UpdatePanic() noexcept {
const auto em = ImGui::GetFontSize();
ImGui::SetNextWindowSize({16*em, 12*em}, ImGuiCond_Appearing);
ImGui::SetNextWindowSize({32*em, 24*em}, ImGuiCond_Appearing);
if (ImGui::BeginPopupModal("panic")) {
ImGui::TextUnformatted("something went wrong X(");
@@ -271,14 +262,15 @@ class Env final : public nf7::Env {
}
void AsyncThread() noexcept {
while (alive_) {
while (auto task = async_.Pop())
const auto now = Clock::now();
while (auto task = async_.Pop(now))
try {
task->second();
} catch (Exception&) {
// TODO: how to handle?
} catch (Exception& e) {
std::cout << "async thread exception: " << e.msg() << std::endl;
}
if (!alive_) break;
async_.WaitFor(1s);
async_.Wait();
}
}
};
@@ -322,7 +314,7 @@ int main(int, char**) {
io.IniFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui::StyleColorsDark();
SetUpImGuiStyle();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
@@ -330,7 +322,7 @@ int main(int, char**) {
{
::Env env;
glfwShowWindow(window);
while (!glfwWindowShouldClose(window)) {
while (!glfwWindowShouldClose(window) && !env.exitRequested()) {
// new frame
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();

129
nf7.cc
View File

@@ -3,20 +3,21 @@
#include <algorithm>
#include <cassert>
#include <map>
#include <sstream>
#include <imgui.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "common/generic_context.hh"
using namespace std::literals;
namespace nf7 {
static std::vector<Env*> env_stack_;
// static variable in function ensures that entity is initialized before using
static inline auto& registry_() noexcept {
static std::map<std::string, const File::TypeInfo*> registry_;
@@ -25,10 +26,32 @@ static inline auto& registry_() noexcept {
void Exception::UpdatePanic() const noexcept {
ImGui::BeginGroup();
ImGui::TextUnformatted(msg_.c_str());
ImGui::Indent();
ImGui::Text("from %s:%d", srcloc_.file_name(), srcloc_.line());
ImGui::Unindent();
ImGui::EndGroup();
}
std::string Exception::Stringify() const noexcept {
return msg() + "\n "+srcloc_.file_name()+":"+std::to_string(srcloc_.line());
}
std::string Exception::StringifyRecursive() const noexcept {
std::stringstream st;
st << Stringify() << "\n";
auto ptr = reason();
while (ptr)
try {
std::rethrow_exception(ptr);
} catch (nf7::Exception& e) {
st << e.Stringify() << "\n";
ptr = e.reason();
} catch (std::exception& e) {
st << e.what() << "\n";
ptr = nullptr;
}
return st.str();
}
const std::map<std::string, const File::TypeInfo*>& File::registry() noexcept {
@@ -70,14 +93,24 @@ void File::MakeAsRoot() noexcept {
}
void File::Isolate() noexcept {
assert(id_ != 0);
const auto pid = id_;
Handle({ .id = id_, .type = File::Event::kRemove });
env_->RemoveFile(id_);
id_ = 0;
parent_ = nullptr;
name_ = "";
Handle({ .id = pid, .type = File::Event::kRemove });
}
void File::Touch() noexcept {
if (std::exchange(touch_, true)) {
return;
}
env().ExecMain(std::make_shared<nf7::GenericContext>(*this), [this]() {
if (id()) {
env().Handle( {.id = id(), .type = Event::kUpdate});
}
touch_ = false;
});
}
File& File::FindOrThrow(std::string_view name) const {
if (auto ret = Find(name)) return *ret;
@@ -142,10 +175,6 @@ File& File::ancestorOrThrow(size_t dist) const {
if (!f) throw NotFoundException("cannot go up over the root");
return const_cast<File&>(*f);
}
void File::Touch() noexcept {
if (!id()) return;
env().Handle({.id = id(), .type = Event::kUpdate});
}
File::TypeInfo::TypeInfo(const std::string& name,
std::unordered_set<std::string>&& flags) noexcept :
@@ -212,27 +241,14 @@ void File::Path::Validate() const {
for (const auto& term : terms_) ValidateTerm(term);
}
Context::Context(File& f) noexcept : Context(f.env(), f.id()) {
Context::Context(File& f, const std::shared_ptr<Context>& parent) noexcept :
Context(f.env(), f.id(), parent) {
}
Context::Context(Env& env, File::Id initiator) noexcept :
env_(&env), initiator_(initiator) {
env_->AddContext(*this);
Context::Context(Env& env, File::Id initiator, const std::shared_ptr<Context>& parent) noexcept :
env_(&env), initiator_(initiator),
parent_(parent), depth_(parent? parent->depth()+1: 0) {
}
Context::~Context() noexcept {
env_->RemoveContext(*this);
}
void Context::AddChild(const std::shared_ptr<Context>& ctx) noexcept {
children_.emplace_back(ctx);
}
void Env::Push(Env& env) noexcept {
env_stack_.push_back(&env);
}
Env& Env::Peek() noexcept {
return *env_stack_.back();
}
void Env::Pop() noexcept {
env_stack_.pop_back();
}
File& Env::GetFileOrThrow(File::Id id) const {
@@ -249,4 +265,63 @@ void Env::Watcher::Watch(File::Id id) noexcept {
env_->AddWatcher(id, *this);
}
SerializerStream::SerializerStream(const char* path, const char* mode) :
fp_(std::fopen(path, mode)), off_(0) {
if (!fp_) {
throw nf7::Exception {"failed to open file: "+std::string {path}};
}
if (0 != std::fseek(fp_, 0, SEEK_END)) {
throw nf7::Exception {"failed to seek file: "+std::string {path}};
}
size_ = static_cast<size_t>(std::ftell(fp_));
if (0 != std::fseek(fp_, 0, SEEK_SET)) {
throw nf7::Exception {"failed to seek file: "+std::string {path}};
}
}
Serializer::ChunkGuard::ChunkGuard(nf7::Serializer& ar) : ar_(&ar) {
ar_->st_->Seek(ar_->st_->offset()+sizeof(uint64_t));
begin_ = ar_->st_->offset();
}
Serializer::ChunkGuard::~ChunkGuard() noexcept {
try {
const auto end = ar_->st_->offset();
ar_->st_->Seek(begin_-sizeof(uint64_t));
*ar_ & static_cast<uint64_t>(end - begin_);
ar_->st_->Seek(end);
} catch (nf7::Exception&) {
// TODO
}
}
Deserializer::ChunkGuard::ChunkGuard(nf7::Deserializer& ar) : ar_(&ar) {
*ar_ & expect_;
begin_ = ar_->st_->offset();
}
Deserializer::ChunkGuard::ChunkGuard(nf7::Deserializer& ar, nf7::Env& env) :
ChunkGuard(ar) {
env_prev_ = ar_->env_;
ar_->env_ = &env;
}
Deserializer::ChunkGuard::~ChunkGuard() {
try {
if (env_prev_) {
ar_->env_ = env_prev_;
}
const auto end = begin_ + expect_;
if (ar_->st_->offset() != end) {
ar_->st_->Seek(end);
}
} catch (nf7::Exception&) {
// TODO
}
}
void Deserializer::ChunkGuard::ValidateEnd() {
if (begin_+expect_ != ar_->st_->offset()) {
throw nf7::DeserializeException {"invalid chunk size"};
}
}
} // namespace nf7

221
nf7.hh
View File

@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <functional>
@@ -25,8 +26,9 @@ class File;
class Context;
class Env;
using Serializer = yas::binary_oarchive<yas::file_ostream, yas::binary>;
using Deserializer = yas::binary_iarchive<yas::file_istream, yas::binary>;
class SerializerStream;
class Serializer;
class Deserializer;
class Exception : public std::nested_exception {
public:
@@ -41,6 +43,9 @@ class Exception : public std::nested_exception {
Exception& operator=(Exception&&) = delete;
virtual void UpdatePanic() const noexcept;
virtual std::string Stringify() const noexcept;
std::string StringifyRecursive() const noexcept;
const std::string& msg() const noexcept { return msg_; }
const std::source_location& srcloc() const noexcept { return srcloc_; }
@@ -89,6 +94,8 @@ class File {
void MakeAsRoot() noexcept;
void Isolate() noexcept;
void Touch() noexcept;
virtual void Update() noexcept { }
virtual void Handle(const Event&) noexcept { }
@@ -117,9 +124,6 @@ class File {
File* parent() const noexcept { return parent_; }
const std::string& name() const noexcept { return name_; }
protected:
void Touch() noexcept;
private:
const TypeInfo* const type_;
Env* const env_;
@@ -127,6 +131,8 @@ class File {
Id id_ = 0;
File* parent_ = nullptr;
std::string name_;
bool touch_ = false;
};
struct File::Event final {
public:
@@ -154,8 +160,10 @@ class File::TypeInfo {
TypeInfo& operator=(const TypeInfo&) = delete;
TypeInfo& operator=(TypeInfo&&) = delete;
virtual std::unique_ptr<File> Deserialize(Env&, Deserializer&) const = 0;
virtual std::unique_ptr<File> Create(Env&) const noexcept = 0;
virtual std::unique_ptr<File> Deserialize(Deserializer&) const = 0;
virtual std::unique_ptr<File> Create(Env&) const = 0;
virtual void UpdateTooltip() const noexcept = 0;
const std::string& name() const noexcept { return name_; }
const std::unordered_set<std::string>& flags() const noexcept { return flags_; }
@@ -215,8 +223,8 @@ class File::NotImplementedException : public Exception {
class Context {
public:
Context() = delete;
Context(File&) noexcept;
Context(Env&, File::Id) noexcept;
Context(File&, const std::shared_ptr<Context>& = nullptr) noexcept;
Context(Env&, File::Id, const std::shared_ptr<Context>& = nullptr) noexcept;
virtual ~Context() noexcept;
virtual void CleanUp() noexcept { }
@@ -227,29 +235,26 @@ class Context {
Env& env() const noexcept { return *env_; }
File::Id initiator() const noexcept { return initiator_; }
std::span<const std::weak_ptr<Context>> children() const noexcept { return children_; }
protected:
void AddChild(const std::shared_ptr<Context>&) noexcept;
std::shared_ptr<Context> parent() const noexcept { return parent_.lock(); }
size_t depth() const noexcept { return depth_; }
private:
Env* const env_;
const File::Id initiator_;
std::vector<std::weak_ptr<Context>> children_;
const std::weak_ptr<Context> parent_;
const size_t depth_;
};
class Env {
public:
friend class ProxyEnv;
using Clock = std::chrono::system_clock;
using Time = Clock::time_point;
class Watcher;
static void Push(Env&) noexcept;
static Env& Peek() noexcept;
static void Pop() noexcept;
Env() = delete;
Env(const std::filesystem::path& npath) noexcept : npath_(npath) {
}
@@ -266,11 +271,15 @@ class Env {
using Task = std::function<void()>;
virtual void ExecMain(const std::shared_ptr<Context>&, Task&&) noexcept = 0;
virtual void ExecSub(const std::shared_ptr<Context>&, Task&&) noexcept = 0;
virtual void ExecAsync(const std::shared_ptr<Context>&, Task&&) noexcept = 0;
virtual void ExecAsync(const std::shared_ptr<Context>&, Task&&, Time = {}) noexcept = 0;
virtual void Handle(const File::Event&) noexcept = 0;
// thread-safe
virtual void Exit() noexcept = 0;
virtual void Save() noexcept = 0;
virtual void Throw(std::exception_ptr&&) noexcept = 0;
const std::filesystem::path& npath() const noexcept { return npath_; }
@@ -279,10 +288,6 @@ class Env {
virtual File::Id AddFile(File&) noexcept = 0;
virtual void RemoveFile(File::Id) noexcept = 0;
friend class nf7::Context;
virtual void AddContext(Context&) noexcept = 0;
virtual void RemoveContext(Context&) noexcept = 0;
virtual void AddWatcher(File::Id, Watcher&) noexcept = 0;
virtual void RemoveWatcher(Watcher&) noexcept = 0;
@@ -306,4 +311,172 @@ class Env::Watcher {
Env* const env_;
};
class SerializerStream final {
public:
SerializerStream(const char* path, const char* mode);
~SerializerStream() noexcept {
std::fclose(fp_);
}
// serializer requires write/flush
size_t write(const void* ptr, size_t size) {
const auto ret = static_cast<size_t>(std::fwrite(ptr, 1, size, fp_));
off_ += ret;
return ret;
}
void flush() {
std::fflush(fp_);
}
// deserializer requires read/available/empty/peekch/getch/ungetch
size_t read(void* ptr, size_t size) {
const auto ret = static_cast<size_t>(std::fread(ptr, 1, size, fp_));
off_ += ret;
return ret;
}
size_t available() const {
return size_ - off_;
}
bool empty() const {
return available() == 0;
}
char peekch() const {
const auto c = std::getc(fp_);
std::ungetc(c, fp_);
return static_cast<char>(c);
}
char getch() {
return static_cast<char>(std::getc(fp_));
}
void ungetch(char c) {
std::ungetc(c, fp_);
}
void Seek(size_t off) {
if (0 != std::fseek(fp_, static_cast<long int>(off), SEEK_SET)) {
throw nf7::Exception {"failed to seek"};
}
off_ = off;
}
size_t offset() const noexcept { return off_; }
private:
std::FILE* fp_;
size_t off_;
size_t size_;
};
class Serializer final :
public yas::detail::binary_ostream<nf7::SerializerStream, yas::binary>,
public yas::detail::oarchive_header<yas::binary> {
public:
using this_type = Serializer;
class ChunkGuard final {
public:
ChunkGuard(nf7::Serializer&);
ChunkGuard(nf7::Serializer& ar, nf7::Env&) : ChunkGuard(ar) { }
~ChunkGuard() noexcept; // use Env::Throw to handle errors
private:
Serializer* const ar_;
size_t begin_;
};
static void Save(const char* path, auto& v) {
SerializerStream st {path, "wb"};
Serializer ar {st};
ar(v);
}
Serializer(nf7::SerializerStream& st) :
binary_ostream(st), oarchive_header(st), st_(&st) {
}
template<typename T>
Serializer& operator& (const T& v) {
return yas::detail::serializer<
yas::detail::type_properties<T>::value,
yas::detail::serialization_method<T, this_type>::value, yas::binary, T>::
save(*this, v);
}
Serializer& serialize() {
return *this;
}
template<typename Head, typename... Tail>
Serializer& serialize(const Head& head, const Tail&... tail) {
return operator& (head).serialize(tail...);
}
template<typename... Args>
Serializer& operator()(const Args&... args) {
return serialize(args...);
}
private:
nf7::SerializerStream* const st_;
};
class Deserializer final :
public yas::detail::binary_istream<nf7::SerializerStream, yas::binary>,
public yas::detail::iarchive_header<yas::binary> {
public:
using this_type = Deserializer;
class ChunkGuard final {
public:
ChunkGuard(Deserializer& ar);
ChunkGuard(Deserializer&, nf7::Env&);
~ChunkGuard() noexcept; // use Env::Throw to handle errors
void ValidateEnd();
private:
Deserializer* const ar_;
size_t expect_;
size_t begin_;
nf7::Env* env_prev_ = nullptr;
};
static void Load(nf7::Env& env, const char* path, auto& v) {
try {
SerializerStream st {path, "rb"};
Deserializer ar {env, st};
ar(v);
} catch (nf7::Exception&) {
throw;
} catch (std::exception&) {
throw nf7::Exception {"deserialization failure"};
}
}
Deserializer(nf7::Env& env, SerializerStream& st) :
binary_istream(st), iarchive_header(st), env_(&env), st_(&st) {
}
template<typename T>
Deserializer& operator& (T&& v) {
using RealType =
typename std::remove_reference<typename std::remove_const<T>::type>::type;
return yas::detail::serializer<
yas::detail::type_properties<RealType>::value,
yas::detail::serialization_method<RealType, Deserializer>::value,
yas::binary, RealType>
::load(*this, v);
}
Deserializer& serialize() {
return *this;
}
template<typename Head, typename... Tail>
Deserializer& serialize(Head&& head, Tail&&... tail) {
return operator& (std::forward<Head>(head)).serialize(std::forward<Tail>(tail)...);
}
template<typename... Args>
Deserializer& operator()(Args&& ... args) {
return serialize(std::forward<Args>(args)...);
}
Env& env() const noexcept { return *env_; }
private:
nf7::Env* env_;
nf7::SerializerStream* const st_;
};
} // namespace nf7

94
theme.hh Normal file
View File

@@ -0,0 +1,94 @@
#pragma once
#include <imgui.h>
inline void SetUpImGuiStyle() noexcept {
// Visual Studio style by MomoDeve from ImThemes
ImGuiStyle& style = ImGui::GetStyle();
style.Alpha = 1.0f;
style.DisabledAlpha = 0.6000000238418579f;
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.WindowRounding = 0.0f;
style.WindowBorderSize = 1.0f;
style.WindowMinSize = ImVec2(32.0f, 32.0f);
style.WindowTitleAlign = ImVec2(0.0f, 0.5f);
style.WindowMenuButtonPosition = ImGuiDir_Left;
style.ChildRounding = 0.0f;
style.ChildBorderSize = 1.0f;
style.PopupRounding = 0.0f;
style.PopupBorderSize = 1.0f;
style.FramePadding = ImVec2(4.0f, 3.0f);
style.FrameRounding = 0.0f;
style.FrameBorderSize = 0.0f;
style.ItemSpacing = ImVec2(8.0f, 4.0f);
style.ItemInnerSpacing = ImVec2(4.0f, 4.0f);
style.CellPadding = ImVec2(4.0f, 2.0f);
style.IndentSpacing = 21.0f;
style.ColumnsMinSpacing = 6.0f;
style.ScrollbarSize = 14.0f;
style.ScrollbarRounding = 0.0f;
style.GrabMinSize = 10.0f;
style.GrabRounding = 0.0f;
style.TabRounding = 0.0f;
style.TabBorderSize = 0.0f;
style.TabMinWidthForCloseButton = 0.0f;
style.ColorButtonPosition = ImGuiDir_Right;
style.ButtonTextAlign = ImVec2(0.5f, 0.5f);
style.SelectableTextAlign = ImVec2(0.0f, 0.0f);
style.Colors[ImGuiCol_Text] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.5921568870544434f, 0.5921568870544434f, 0.5921568870544434f, 1.0f);
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_ChildBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_PopupBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_Border] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.321568638086319f, 0.321568638086319f, 0.3333333432674408f, 1.0f);
style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.3529411852359772f, 0.3529411852359772f, 0.3725490272045135f, 1.0f);
style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.3529411852359772f, 0.3529411852359772f, 0.3725490272045135f, 1.0f);
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_Button] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_Header] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_Separator] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.3058823645114899f, 0.3058823645114899f, 0.3058823645114899f, 1.0f);
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.2000000029802322f, 0.2000000029802322f, 0.2156862765550613f, 1.0f);
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.321568638086319f, 0.321568638086319f, 0.3333333432674408f, 1.0f);
style.Colors[ImGuiCol_Tab] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_TabHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_TabActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_PlotLines] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.1137254908680916f, 0.5921568870544434f, 0.9254902005195618f, 1.0f);
style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.1882352977991104f, 0.1882352977991104f, 0.2000000029802322f, 1.0f);
style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.3098039329051971f, 0.3098039329051971f, 0.3490196168422699f, 1.0f);
style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.2274509817361832f, 0.2274509817361832f, 0.2470588237047195f, 1.0f);
style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.05999999865889549f);
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.0f, 0.4666666686534882f, 0.7843137383460999f, 1.0f);
style.Colors[ImGuiCol_DragDropTarget] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 1.0f, 1.0f, 0.699999988079071f);
style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.800000011920929f, 0.800000011920929f, 0.800000011920929f, 0.2000000029802322f);
style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.1450980454683304f, 0.1450980454683304f, 0.1490196138620377f, 1.0f);
}

View File

@@ -1,3 +0,0 @@
add_executable(nf7-init)
target_link_libraries(nf7-init PRIVATE yas)
target_sources(nf7-init PRIVATE init.cc)

View File

@@ -1,69 +0,0 @@
#include <cassert>
#include <functional>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include <unordered_set>
#include <yas/serialize.hpp>
#include <yas/types/std/map.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/unordered_set.hpp>
using namespace std::literals;
using Ar = yas::binary_oarchive<yas::mem_ostream>;
using L = std::function<void(void)>;
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
L> {
public:
static Ar& save(Ar& ar, const L& f) {
f();
return ar;
}
};
}
template <typename... Args>
L Write(Ar& ar, const Args&... args) {
return [&args..., &ar]() { ar(args...); };
}
int main(void) {
yas::mem_ostream os;
Ar ar(os);
# define WINDOW_(shown) shown
ar("System/Dir"s, std::map<std::string, L> {
{ "_audio"s,
Write(ar, "Audio/Context"s) },
{ "_imgui"s,
Write(ar, "System/ImGuiConfig"s, ""s) },
{ "_logger"s,
Write(ar, "System/Logger"s, WINDOW_(true), 1024, false, false) },
{ "_luajit"s,
Write(ar, "LuaJIT/Context"s) },
{ "home"s,
Write(ar, "System/Dir"s,
std::map<std::string, L> {},
std::unordered_set<std::string> {},
WINDOW_(false)) },
}, std::unordered_set<std::string> {}, WINDOW_(true));
const auto buf = os.get_shared_buffer();
for (size_t i = 0; i < buf.size;) {
for (size_t j = 0; j < 32 && i < buf.size; ++j, ++i) {
std::cout << static_cast<int>(buf.data.get()[i]) << ',';
}
}
std::cout << std::endl;
}