Compare commits
No commits in common. "v0.2.0" and "main" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
/build/
|
||||
/build*/
|
||||
|
200
CMakeLists.txt
200
CMakeLists.txt
@ -1,168 +1,54 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
|
||||
# ---- configuration ----
|
||||
project(nf7 C CXX)
|
||||
project(nf7 C)
|
||||
|
||||
option(NF7_STATIC "link all libs statically" ON)
|
||||
option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(NF7_OPTIONS_WARNING
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
||||
-Wall -Werror -pedantic-errors -Wextra -Wconversion -Wsign-conversion>
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
|
||||
-Wno-overloaded-virtual>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:
|
||||
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
|
||||
)
|
||||
if (NF7_SANITIZE_THREAD)
|
||||
set(NF7_OPTIONS_SANITIZE
|
||||
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
|
||||
-fsanitize=thread -fno-omit-frame-pointer>>
|
||||
)
|
||||
else()
|
||||
set(NF7_OPTIONS_SANITIZE
|
||||
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
|
||||
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
|
||||
)
|
||||
endif()
|
||||
|
||||
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
|
||||
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
include(tool/meta.cmake)
|
||||
|
||||
# ---- thirdparty import
|
||||
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
|
||||
|
||||
# ---- common config
|
||||
add_library(nf7config INTERFACE)
|
||||
target_include_directories(nf7config
|
||||
INTERFACE
|
||||
${PROJECT_SOURCE_DIR}
|
||||
${NF7_GENERATED_DIR}
|
||||
)
|
||||
target_compile_options(nf7config INTERFACE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:
|
||||
/W4
|
||||
$<$<CONFIG:Debug>:/WX>
|
||||
>
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:
|
||||
-Wall -Wextra -Wpedantic
|
||||
-Wno-gnu-zero-variadic-macro-arguments
|
||||
$<$<CONFIG:Debug>:-Werror>
|
||||
>
|
||||
)
|
||||
|
||||
# ---- application ----
|
||||
# ---- test library
|
||||
add_subdirectory(test EXCLUDE_FROM_ALL)
|
||||
|
||||
# ---- util library
|
||||
add_subdirectory(util)
|
||||
|
||||
# ---- interface library
|
||||
add_library(nf7if INTERFACE)
|
||||
target_sources(nf7if INTERFACE nf7.h)
|
||||
target_link_libraries(nf7if INTERFACE nf7config nf7util uv)
|
||||
|
||||
# ---- core library
|
||||
add_subdirectory(core)
|
||||
|
||||
# ---- main executable
|
||||
add_executable(nf7)
|
||||
target_include_directories(nf7 PRIVATE . "${PROJECT_BINARY_DIR}/include")
|
||||
target_compile_options(nf7 PRIVATE
|
||||
${NF7_OPTIONS_WARNING}
|
||||
${NF7_OPTIONS_SANITIZE}
|
||||
)
|
||||
target_link_options(nf7 PRIVATE
|
||||
${NF7_OPTIONS_SANITIZE}
|
||||
)
|
||||
target_compile_definitions(nf7
|
||||
PRIVATE
|
||||
IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
$<$<PLATFORM_ID:Darwin>:GL_SILENCE_DEPRECATION>
|
||||
$<$<PLATFORM_ID:Darwin>:_GNU_SOURCE>
|
||||
)
|
||||
target_sources(nf7
|
||||
PRIVATE
|
||||
init.hh
|
||||
main.cc
|
||||
nf7.cc
|
||||
nf7.hh
|
||||
theme.hh
|
||||
|
||||
common/aggregate_command.hh
|
||||
common/audio_queue.hh
|
||||
common/dir.hh
|
||||
common/dir_item.hh
|
||||
common/file_base.hh
|
||||
common/file_holder.hh
|
||||
common/file_holder.cc
|
||||
common/future.hh
|
||||
common/generic_context.hh
|
||||
common/generic_history.hh
|
||||
common/generic_memento.hh
|
||||
common/generic_type_info.hh
|
||||
common/generic_watcher.hh
|
||||
common/gui_dnd.hh
|
||||
common/gui_config.hh
|
||||
common/gui_context.hh
|
||||
common/gui_file.hh
|
||||
common/gui_file.cc
|
||||
common/gui_node.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/life.hh
|
||||
common/logger.hh
|
||||
common/logger_ref.hh
|
||||
common/luajit.hh
|
||||
common/luajit.cc
|
||||
common/luajit_queue.hh
|
||||
common/luajit_ref.hh
|
||||
common/luajit_thread.hh
|
||||
common/luajit_thread.cc
|
||||
common/memento.hh
|
||||
common/memento_recorder.hh
|
||||
common/mutable_memento.hh
|
||||
common/mutex.hh
|
||||
common/nfile.hh
|
||||
common/nfile_watcher.hh
|
||||
common/node.hh
|
||||
common/node_link_store.hh
|
||||
common/node_root_lambda.hh
|
||||
common/node_root_select_lambda.hh
|
||||
common/ptr_selector.hh
|
||||
common/queue.hh
|
||||
common/ring_buffer.hh
|
||||
common/sequencer.hh
|
||||
common/squashed_history.hh
|
||||
common/task.hh
|
||||
common/thread.hh
|
||||
common/timed_queue.hh
|
||||
common/util_algorithm.hh
|
||||
common/util_string.hh
|
||||
common/value.hh
|
||||
common/yas_enum.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/nfile_unix.cc>
|
||||
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
|
||||
|
||||
file/audio_context.cc
|
||||
file/audio_device.cc
|
||||
file/luajit_context.cc
|
||||
file/luajit_inline_node.cc
|
||||
file/luajit_node.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_event.cc
|
||||
file/system_imgui.cc
|
||||
file/system_logger.cc
|
||||
file/system_nfile.cc
|
||||
file/value_curve.cc
|
||||
file/value_plot.cc
|
||||
)
|
||||
target_sources(nf7 PRIVATE main.c)
|
||||
target_link_libraries(nf7
|
||||
PRIVATE
|
||||
glew
|
||||
glfw
|
||||
imgui
|
||||
imnodes
|
||||
implot
|
||||
linalg.h
|
||||
luajit
|
||||
magic_enum
|
||||
miniaudio
|
||||
source_location
|
||||
yas
|
||||
yaml-cpp
|
||||
nf7if
|
||||
nf7core
|
||||
)
|
||||
|
13
LICENSE
13
LICENSE
@ -1,13 +0,0 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
17
README.md
17
README.md
@ -1,8 +1,19 @@
|
||||
nf7
|
||||
Nf7
|
||||
====
|
||||
|
||||
node-based programming language
|
||||
Nf7 is an abstraction layer for Media-programming.
|
||||
The goal is to allow creative activities on any platforms without any differences.
|
||||
|
||||
## LICENSE
|
||||
**UNDER REFACTORING:** Please checkout tag v0.4.1 to see a codebase which works.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
$ cmake -B build
|
||||
$ cmake --build build -j4
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
WTFPLv2
|
||||
|
||||
|
@ -1,79 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class AggregateCommand : public nf7::History::Command {
|
||||
public:
|
||||
using CommandList = std::vector<std::unique_ptr<Command>>;
|
||||
|
||||
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 {
|
||||
Exec(commands_.begin(), commands_.end(),
|
||||
[](auto& a) { a->Apply(); },
|
||||
[](auto& a) { a->Revert(); });
|
||||
applied_ = true;
|
||||
}
|
||||
void Revert() override {
|
||||
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 < end) {
|
||||
apply(*itr);
|
||||
++itr;
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
throw History::CorruptException("failed to revert AggregateCommand");
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
try {
|
||||
while (itr > begin) {
|
||||
--itr;
|
||||
revert(*itr);
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
throw History::CorruptException(
|
||||
"AggregateCommand gave up recovering from failure of revert");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <miniaudio.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7::audio {
|
||||
|
||||
class Queue : public nf7::File::Interface {
|
||||
public:
|
||||
using Task = std::function<void(ma_context*)>;
|
||||
|
||||
Queue() = default;
|
||||
virtual ~Queue() = default;
|
||||
Queue(const Queue&) = delete;
|
||||
Queue(Queue&&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
Queue& operator=(Queue&&) = delete;
|
||||
|
||||
// thread-safe
|
||||
// WARNING: when failed to create ma_context, nullptr is passed
|
||||
virtual void Push(const std::shared_ptr<nf7::Context>&, Task&&) noexcept = 0;
|
||||
|
||||
virtual std::shared_ptr<Queue> self() noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7::audio
|
@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Dir : public File::Interface {
|
||||
public:
|
||||
class DuplicateException;
|
||||
|
||||
Dir() = default;
|
||||
|
||||
virtual File& Add(std::string_view, std::unique_ptr<File>&&) = 0;
|
||||
virtual std::unique_ptr<File> Remove(std::string_view) noexcept = 0;
|
||||
};
|
||||
class Dir::DuplicateException : public Exception {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class DirItem : public File::Interface {
|
||||
public:
|
||||
enum Flag : uint8_t {
|
||||
kNone = 0,
|
||||
kTree = 1 << 0,
|
||||
kMenu = 1 << 1,
|
||||
kTooltip = 1 << 2,
|
||||
kWidget = 1 << 3,
|
||||
kDragDropTarget = 1 << 4,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
DirItem() = delete;
|
||||
DirItem(Flags flags) noexcept : flags_(flags) {
|
||||
}
|
||||
DirItem(const DirItem&) = delete;
|
||||
DirItem(DirItem&&) = delete;
|
||||
DirItem& operator=(const DirItem&) = delete;
|
||||
DirItem& operator=(DirItem&&) = delete;
|
||||
|
||||
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_; }
|
||||
|
||||
private:
|
||||
Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,62 +0,0 @@
|
||||
#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
|
@ -1,134 +0,0 @@
|
||||
#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
|
@ -1,188 +0,0 @@
|
||||
#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
|
362
common/future.hh
362
common/future.hh
@ -1,362 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <coroutine>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/generic_context.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
// How To Use (factory side)
|
||||
// 1. Create Future<T>::Promise. (T is a type of returned value)
|
||||
// 2. Get Future<T> from Future<T>::Promise and Pass it to ones who want to get T.
|
||||
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
|
||||
//
|
||||
// Users who receive Future can wait for finishing
|
||||
// by Future::Then(), Future::ThenIf(), Future::Catch(), or co_await.
|
||||
|
||||
|
||||
class CoroutineAbortException final : public nf7::Exception {
|
||||
public:
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
class Future final {
|
||||
public:
|
||||
class Promise;
|
||||
class Coro;
|
||||
|
||||
using ThisFuture = nf7::Future<T>;
|
||||
using Handle = std::coroutine_handle<Promise>;
|
||||
using Imm = std::variant<T, std::exception_ptr>;
|
||||
|
||||
enum State { kYet, kDone, kError, };
|
||||
|
||||
// A data shared between Future, Promise, and Coro.
|
||||
// One per one Promise.
|
||||
struct Data final {
|
||||
public:
|
||||
std::weak_ptr<nf7::Context> ctx;
|
||||
|
||||
std::atomic<bool> destroyed = false;
|
||||
std::atomic<bool> aborted = false;
|
||||
std::atomic<size_t> pros = 0;
|
||||
std::atomic<State> state = kYet;
|
||||
|
||||
std::mutex mtx;
|
||||
std::optional<T> value;
|
||||
std::exception_ptr exception;
|
||||
std::vector<std::function<void()>> recv;
|
||||
};
|
||||
|
||||
// Factory side have this to tell finish or abort.
|
||||
class Promise final {
|
||||
public:
|
||||
// Use data_() instead, MSVC can't understand the followings:
|
||||
// template <typename U> friend class nf7::Future<U>;
|
||||
// template <typename U> friend class nf7::Future<U>::Coro;
|
||||
|
||||
static constexpr bool kThisIsNf7FuturePromise = true;
|
||||
|
||||
Promise() noexcept : data_(std::make_shared<Data>()) {
|
||||
++data_->pros;
|
||||
}
|
||||
Promise(const std::shared_ptr<nf7::Context>& ctx) noexcept : Promise() {
|
||||
data_->ctx = ctx;
|
||||
}
|
||||
Promise(const Promise& src) noexcept : data_(src.data_) {
|
||||
++data_->pros;
|
||||
}
|
||||
Promise(Promise&&) = default;
|
||||
Promise& operator=(const Promise& src) noexcept {
|
||||
data_ = src.data_;
|
||||
++data_->pros;
|
||||
}
|
||||
Promise& operator=(Promise&&) = default;
|
||||
~Promise() noexcept {
|
||||
if (data_ && --data_->pros == 0 && data_->state == kYet) {
|
||||
Throw(std::make_exception_ptr<nf7::Exception>({"promise forgotten"}));
|
||||
}
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
auto Return(T&& v) noexcept {
|
||||
std::unique_lock<std::mutex> k(data_->mtx);
|
||||
if (data_->state == kYet) {
|
||||
data_->value = std::move(v);
|
||||
data_->state = kDone;
|
||||
CallReceivers();
|
||||
}
|
||||
}
|
||||
auto Return(const T& v) noexcept {
|
||||
Return(T {v});
|
||||
}
|
||||
// thread-safe
|
||||
void Throw(std::exception_ptr e) noexcept {
|
||||
std::unique_lock<std::mutex> k(data_->mtx);
|
||||
if (data_->state == kYet) {
|
||||
data_->exception = e;
|
||||
data_->state = kError;
|
||||
CallReceivers();
|
||||
}
|
||||
}
|
||||
template <typename E, typename... Args>
|
||||
void Throw(Args&&... args) noexcept {
|
||||
return Throw(std::make_exception_ptr<E>(E {std::forward<Args>(args)...}));
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
// Do Return(f()) if no exception is thrown, otherwise call Throw().
|
||||
auto Wrap(const std::function<T()>& f) noexcept
|
||||
try {
|
||||
Return(f());
|
||||
} catch (...) {
|
||||
Throw(std::current_exception());
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
// Creates Future() object.
|
||||
ThisFuture future() const noexcept {
|
||||
assert(data_);
|
||||
return ThisFuture(data_);
|
||||
}
|
||||
|
||||
auto get_return_object() noexcept {
|
||||
return Coro(Handle::from_promise(*this), data_);
|
||||
}
|
||||
auto initial_suspend() const noexcept {
|
||||
return std::suspend_always();
|
||||
}
|
||||
auto final_suspend() const noexcept {
|
||||
return std::suspend_always();
|
||||
}
|
||||
auto yield_value(const T& v) {
|
||||
Return(T(v));
|
||||
return std::suspend_never();
|
||||
}
|
||||
auto yield_value(T&& v) {
|
||||
Return(std::move(v));
|
||||
return std::suspend_never();
|
||||
}
|
||||
auto return_void() {
|
||||
if (data_->state == kYet) {
|
||||
if constexpr (std::is_same<T, std::monostate>::value) {
|
||||
Return({});
|
||||
} else {
|
||||
assert(false && "coroutine returned without value");
|
||||
}
|
||||
}
|
||||
}
|
||||
auto unhandled_exception() noexcept {
|
||||
Throw(std::current_exception());
|
||||
}
|
||||
|
||||
const std::shared_ptr<Data>& data__() noexcept { return data_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Data> data_;
|
||||
|
||||
void CallReceivers() noexcept {
|
||||
for (auto recv : data_->recv) recv();
|
||||
data_->recv.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function returning Coro to implement a factory with coroutine.
|
||||
class Coro final {
|
||||
public:
|
||||
friend Promise;
|
||||
using promise_type = Promise;
|
||||
|
||||
Coro() = delete;
|
||||
~Coro() noexcept {
|
||||
if (data_ && !data_->destroyed.exchange(true)) {
|
||||
h_.destroy();
|
||||
}
|
||||
}
|
||||
Coro(const Coro&) = delete;
|
||||
Coro(Coro&&) = default;
|
||||
Coro& operator=(const Coro&) = delete;
|
||||
Coro& operator=(Coro&&) = default;
|
||||
|
||||
ThisFuture Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
|
||||
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
|
||||
data_->ctx = ctx;
|
||||
return ThisFuture(data_);
|
||||
}
|
||||
void Abort() noexcept {
|
||||
h_.promise().Throw(
|
||||
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
|
||||
data_->aborted = true;
|
||||
}
|
||||
|
||||
private:
|
||||
Handle h_;
|
||||
std::shared_ptr<Data> data_;
|
||||
|
||||
Coro(Handle h, const std::shared_ptr<Data>& data) noexcept : h_(h), data_(data) { }
|
||||
};
|
||||
|
||||
|
||||
Future(const T& v) noexcept : imm_({v}) {
|
||||
}
|
||||
Future(T&& v) noexcept : imm_({std::move(v)}) {
|
||||
}
|
||||
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 ThisFuture&) = default;
|
||||
Future(ThisFuture&&) = default;
|
||||
Future& operator=(const ThisFuture&) = default;
|
||||
Future& operator=(ThisFuture&&) = default;
|
||||
|
||||
// Schedules to execute f() immediately on any thread
|
||||
// when the promise is finished or aborted.
|
||||
// If ctx is not nullptr, the function will be run synchronized with main thread.
|
||||
ThisFuture& Then(const std::shared_ptr<nf7::Context>& ctx, std::function<void(const ThisFuture&)>&& f) noexcept {
|
||||
auto fun = std::move(f);
|
||||
if (ctx) {
|
||||
fun = [ctx, fun = std::move(fun)](auto& fu) {
|
||||
ctx->env().ExecSub(
|
||||
ctx, [fu, fun = std::move(fun)]() mutable { fun(fu); });
|
||||
};
|
||||
}
|
||||
if (data_) {
|
||||
std::unique_lock<std::mutex> k(data_->mtx);
|
||||
if (yet()) {
|
||||
data_->recv.push_back(
|
||||
[fun = std::move(fun), d = data_]() mutable { fun(ThisFuture {d}); });
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
fun(*this);
|
||||
return *this;
|
||||
}
|
||||
template <typename R>
|
||||
nf7::Future<R> Then(const std::shared_ptr<nf7::Context>& ctx,
|
||||
std::function<void(const ThisFuture&, typename nf7::Future<R>::Promise&)>&& f) noexcept {
|
||||
typename nf7::Future<R>::Promise pro;
|
||||
Then(ctx, [pro, f = std::move(f)](auto& fu) mutable {
|
||||
try {
|
||||
f(fu, pro);
|
||||
} catch (...) {
|
||||
pro.Throw(std::current_exception());
|
||||
}
|
||||
});
|
||||
return pro.future();
|
||||
}
|
||||
ThisFuture& Then(auto&& f) noexcept {
|
||||
return Then(nullptr, std::move(f));
|
||||
}
|
||||
|
||||
// same as Then() but called when it's done without error
|
||||
ThisFuture& ThenIf(const std::shared_ptr<nf7::Context>& ctx, std::function<void(const T&)>&& f) noexcept {
|
||||
Then(ctx, [f = std::move(f)](auto& fu) {
|
||||
if (fu.done()) f(fu.value());
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
ThisFuture& ThenIf(auto&& f) noexcept {
|
||||
return ThenIf(nullptr, std::move(f));
|
||||
}
|
||||
|
||||
// same as Then() but called when it caused an exception
|
||||
template <typename E>
|
||||
ThisFuture& Catch(const std::shared_ptr<nf7::Context>& ctx, std::function<void(E&)>&& f) noexcept {
|
||||
Then(ctx, [f = std::move(f)](auto& fu) {
|
||||
try { fu.value(); } catch (E& e) { f(e); } catch (...) { }
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
template <typename E>
|
||||
ThisFuture& Catch(auto&& f) noexcept {
|
||||
return Catch<E>(nullptr, std::move(f));
|
||||
}
|
||||
|
||||
const auto& value() const {
|
||||
if (imm_) {
|
||||
if (std::holds_alternative<T>(*imm_)) return std::get<T>(*imm_);
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(*imm_));
|
||||
}
|
||||
|
||||
assert(data_);
|
||||
switch (data_->state) {
|
||||
case kYet:
|
||||
assert(false);
|
||||
break;
|
||||
case kDone:
|
||||
return *data_->value;
|
||||
case kError:
|
||||
std::rethrow_exception(data_->exception);
|
||||
}
|
||||
throw 0;
|
||||
}
|
||||
|
||||
bool yet() const noexcept {
|
||||
return !imm_ && data_->state == kYet;
|
||||
}
|
||||
bool done() const noexcept {
|
||||
return (imm_ && std::holds_alternative<T>(*imm_)) || data_->state == kDone;
|
||||
}
|
||||
bool error() const noexcept {
|
||||
return (imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
|
||||
data_->state == kError;
|
||||
}
|
||||
|
||||
bool await_ready() const noexcept { return !yet(); }
|
||||
template <typename U>
|
||||
void await_suspend(std::coroutine_handle<U> caller) const noexcept {
|
||||
static_assert(U::kThisIsNf7FuturePromise, "illegal coroutine");
|
||||
assert(data_);
|
||||
auto& data = *data_;
|
||||
|
||||
std::unique_lock<std::mutex> k(data.mtx);
|
||||
auto callee_ctx = data.ctx.lock();
|
||||
|
||||
auto caller_data = caller.promise().data__();
|
||||
auto caller_ctx = caller_data->ctx.lock();
|
||||
assert(caller_ctx);
|
||||
|
||||
if (yet()) {
|
||||
data.recv.push_back([caller, caller_data, caller_ctx, callee_ctx]() {
|
||||
caller_ctx->env().ExecSub(caller_ctx, [caller, caller_data, caller_ctx]() {
|
||||
if (!caller_data->aborted) {
|
||||
caller.resume();
|
||||
} else {
|
||||
if (!caller_data->destroyed.exchange(true)) {
|
||||
caller.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// promise has ended after await_ready() is called
|
||||
caller.resume();
|
||||
}
|
||||
}
|
||||
auto& await_resume() { return value(); }
|
||||
|
||||
private:
|
||||
std::optional<Imm> imm_;
|
||||
std::shared_ptr<Data> data_;
|
||||
|
||||
Future(const std::shared_ptr<Data>& data) noexcept : data_(data) { }
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericContext : public Context {
|
||||
public:
|
||||
GenericContext(Env& env, File::Id id, std::string_view desc = "") noexcept :
|
||||
Context(env, id), desc_(desc) {
|
||||
}
|
||||
GenericContext(File& f, std::string_view desc = "") noexcept :
|
||||
GenericContext(f.env(), f.id(), desc) {
|
||||
}
|
||||
|
||||
void CleanUp() noexcept override {
|
||||
}
|
||||
void Abort() noexcept override {
|
||||
abort_ = true;
|
||||
}
|
||||
|
||||
size_t GetMemoryUsage() const noexcept override {
|
||||
return mem_;
|
||||
}
|
||||
std::string GetDescription() const noexcept override {
|
||||
return desc_;
|
||||
}
|
||||
|
||||
size_t& memoryUsage() noexcept { return mem_; }
|
||||
std::string& description() noexcept { return desc_; }
|
||||
|
||||
bool aborted() const noexcept { return abort_; }
|
||||
size_t memoryUsage() const noexcept { return mem_; }
|
||||
const std::string& description() const noexcept { return desc_; }
|
||||
|
||||
private:
|
||||
std::atomic<bool> abort_ = false;
|
||||
|
||||
size_t mem_;
|
||||
|
||||
std::string desc_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericHistory : public nf7::History {
|
||||
public:
|
||||
GenericHistory() = default;
|
||||
GenericHistory(const GenericHistory&) = delete;
|
||||
GenericHistory(GenericHistory&&) = default;
|
||||
GenericHistory& operator=(const GenericHistory&) = delete;
|
||||
GenericHistory& operator=(GenericHistory&&) = default;
|
||||
~GenericHistory() noexcept {
|
||||
Clear();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void UnDo() {
|
||||
if (cursor_ <= 0) return;
|
||||
cmds_[cursor_-1]->Revert();
|
||||
--cursor_;
|
||||
}
|
||||
void ReDo() {
|
||||
if (cursor_ >= cmds_.size()) return;
|
||||
cmds_[cursor_]->Apply();
|
||||
++cursor_;
|
||||
}
|
||||
|
||||
Command* prev() const noexcept {
|
||||
return cursor_ > 0? cmds_[cursor_-1].get(): nullptr;
|
||||
}
|
||||
Command* next() const noexcept {
|
||||
return cursor_ < cmds_.size()? cmds_[cursor_].get(): nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Command>> cmds_;
|
||||
|
||||
size_t cursor_ = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/mutable_memento.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename T>
|
||||
class GenericMemento : public nf7::MutableMemento {
|
||||
public:
|
||||
class CustomTag;
|
||||
|
||||
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;
|
||||
last_ = nullptr;
|
||||
assert(map_.empty());
|
||||
}
|
||||
|
||||
T* operator->() noexcept {
|
||||
return &data_;
|
||||
}
|
||||
const T* operator->() const noexcept {
|
||||
return &data_;
|
||||
}
|
||||
|
||||
std::shared_ptr<Tag> Save() noexcept override {
|
||||
if (tag_) return tag_;
|
||||
auto [itr, emplaced] = map_.emplace(next_++, data_);
|
||||
assert(emplaced);
|
||||
return last_ = tag_ = std::make_shared<CustomTag>(*this, itr->first);
|
||||
}
|
||||
void Restore(const std::shared_ptr<Tag>& tag) override {
|
||||
assert(tag);
|
||||
auto itr = map_.find(tag->id());
|
||||
assert(itr != map_.end());
|
||||
data_ = itr->second;
|
||||
tag_ = tag;
|
||||
last_ = tag;
|
||||
onRestore();
|
||||
if (file_) file_->Touch();
|
||||
}
|
||||
void Commit() noexcept override {
|
||||
tag_ = nullptr;
|
||||
onCommit();
|
||||
if (file_) file_->Touch();
|
||||
}
|
||||
void CommitAmend() noexcept override {
|
||||
if (!tag_) return;
|
||||
auto itr = map_.find(tag_->id());
|
||||
assert(itr != map_.end());
|
||||
itr->second = data_;
|
||||
onCommit();
|
||||
if (file_) file_->Touch();
|
||||
}
|
||||
|
||||
T& data() noexcept { return data_; }
|
||||
const T& data() const noexcept { return data_; }
|
||||
|
||||
const T& last() const noexcept {
|
||||
if (!last_) return initial_;
|
||||
|
||||
auto itr = map_.find(last_->id());
|
||||
assert(itr != map_.end());
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
std::function<void()> onRestore = [](){};
|
||||
std::function<void()> onCommit = [](){};
|
||||
|
||||
private:
|
||||
nf7::File* const file_;
|
||||
|
||||
const T initial_;
|
||||
T data_;
|
||||
|
||||
Tag::Id next_ = 0;
|
||||
std::unordered_map<Tag::Id, T> map_;
|
||||
|
||||
std::shared_ptr<nf7::Memento::Tag> tag_;
|
||||
std::shared_ptr<nf7::Memento::Tag> last_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class GenericMemento<T>::CustomTag final : public Tag {
|
||||
public:
|
||||
CustomTag(GenericMemento& owner, Id id) noexcept : Tag(id), owner_(&owner) {
|
||||
}
|
||||
~CustomTag() noexcept {
|
||||
owner_->map_.erase(id());
|
||||
}
|
||||
private:
|
||||
GenericMemento* owner_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,67 +0,0 @@
|
||||
#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>
|
||||
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<nf7::File> Deserialize(nf7::Deserializer& ar) const override
|
||||
try {
|
||||
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(nf7::Env& env) const override {
|
||||
if constexpr (std::is_constructible<T, nf7::Env&>::value) {
|
||||
return std::make_unique<T>(env);
|
||||
} else {
|
||||
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, nf7::Env&>::value) {
|
||||
flags.insert("nf7::File::TypeInfo::Factory");
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericWatcher final : public Env::Watcher {
|
||||
public:
|
||||
using Handler = std::function<void(const File::Event&)>;
|
||||
|
||||
GenericWatcher(Env& env) noexcept : Watcher(env) {
|
||||
}
|
||||
|
||||
void AddHandler(File::Event::Type type, Handler&& h) noexcept {
|
||||
handlers_[type] = std::move(h);
|
||||
}
|
||||
void Handle(const File::Event& ev) noexcept override {
|
||||
auto handler = handlers_.find(ev.type);
|
||||
if (handler == handlers_.end()) return;
|
||||
handler->second(ev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<File::Event::Type, Handler> handlers_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,59 +0,0 @@
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/generic_memento.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
template <typename T>
|
||||
concept ConfigData = requires (T& x) {
|
||||
{ x.Stringify() } -> std::convertible_to<std::string>;
|
||||
x.Parse(std::string {});
|
||||
};
|
||||
|
||||
template <ConfigData T>
|
||||
void Config(nf7::GenericMemento<T>& mem) noexcept {
|
||||
static std::string text_;
|
||||
static std::string msg_;
|
||||
static bool mod_;
|
||||
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
text_ = mem->Stringify();
|
||||
msg_ = "";
|
||||
mod_ = false;
|
||||
}
|
||||
|
||||
mod_ |= ImGui::InputTextMultiline("##config", &text_);
|
||||
|
||||
ImGui::BeginDisabled(!mod_);
|
||||
if (ImGui::Button("apply")) {
|
||||
try {
|
||||
mem->Parse(text_);
|
||||
mem.Commit();
|
||||
msg_ = "";
|
||||
mod_ = false;
|
||||
} catch (nf7::Exception& e) {
|
||||
msg_ = e.msg();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("restore")) {
|
||||
text_ = mem->Stringify();
|
||||
msg_ = "";
|
||||
mod_ = false;
|
||||
}
|
||||
|
||||
if (msg_.size()) {
|
||||
ImGui::Bullet();
|
||||
ImGui::TextUnformatted(msg_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,46 +0,0 @@
|
||||
#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
|
@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7::gui::dnd {
|
||||
|
||||
// data entity is char[] of file path
|
||||
constexpr const char* kFilePath = "nf7::File::Path";
|
||||
|
||||
|
||||
template <typename T>
|
||||
bool Send(const char* type, const T&) noexcept;
|
||||
|
||||
template <>
|
||||
inline bool Send<std::string>(const char* type, const std::string& v) noexcept {
|
||||
return ImGui::SetDragDropPayload(type, v.data(), v.size());
|
||||
}
|
||||
template <>
|
||||
inline bool Send<std::string_view>(const char* type, const std::string_view& v) noexcept {
|
||||
return ImGui::SetDragDropPayload(type, v.data(), v.size());
|
||||
}
|
||||
template <>
|
||||
inline bool Send<File::Path>(const char* type, const File::Path& p) noexcept {
|
||||
return Send(type, p.Stringify());
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T To(const ImGuiPayload&) noexcept;
|
||||
|
||||
template <>
|
||||
inline std::string To<std::string>(const ImGuiPayload& pay) noexcept {
|
||||
std::string ret;
|
||||
ret.resize(static_cast<size_t>(pay.DataSize));
|
||||
std::memcpy(ret.data(), pay.Data, ret.size());
|
||||
return ret;
|
||||
}
|
||||
template <>
|
||||
inline File::Path To<File::Path>(const ImGuiPayload& pay) noexcept {
|
||||
return File::Path::Parse(To<std::string>(pay));
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> Accept(const char* type, ImGuiDragDropFlags flags = 0) noexcept {
|
||||
if (auto pay = ImGui::AcceptDragDropPayload(type, flags)) {
|
||||
return To<T>(*pay);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
template <typename T>
|
||||
const ImGuiPayload* Peek(const char* type, auto& v, ImGuiDragDropFlags flags = 0) noexcept {
|
||||
flags |= ImGuiDragDropFlags_AcceptPeekOnly;
|
||||
if (auto pay = ImGui::AcceptDragDropPayload(type, flags)) {
|
||||
v = To<T>(*pay);
|
||||
return pay;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool IsFirstAccept() noexcept {
|
||||
const auto ctx = ImGui::GetCurrentContext();
|
||||
return ctx->DragDropAcceptFrameCount < ctx->FrameCount;
|
||||
}
|
||||
|
||||
inline void DrawRect() noexcept {
|
||||
auto& r = ImGui::GetCurrentContext()->DragDropTargetRect;
|
||||
ImGui::GetForegroundDrawList()->AddRect(
|
||||
r.Min - ImVec2 {3.5f, 3.5f},
|
||||
r.Max + ImVec2 {3.5f, 3.5f},
|
||||
ImGui::GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);
|
||||
}
|
||||
|
||||
} // namespace nf7::gui::dnd
|
@ -1,252 +0,0 @@
|
||||
#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
|
@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/file_base.hh"
|
||||
#include "common/file_holder.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
class FileFactory final {
|
||||
public:
|
||||
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() 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:
|
||||
nf7::File* const owner_;
|
||||
const Filter filter_;
|
||||
const Flags flags_;
|
||||
|
||||
std::string name_;
|
||||
const nf7::File::TypeInfo* type_ = nullptr;
|
||||
std::string type_filter_;
|
||||
};
|
||||
|
||||
class FileHolderEditor final : public nf7::FileBase::Feature {
|
||||
public:
|
||||
enum Type {
|
||||
kOwn,
|
||||
kRef,
|
||||
};
|
||||
|
||||
FileHolderEditor(nf7::FileHolder& h, FileFactory::Filter&& filter) noexcept :
|
||||
holder_(&h), factory_(h.owner(), std::move(filter)) {
|
||||
}
|
||||
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
|
@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <ImNodes.h>
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
inline void NodeSocket() noexcept {
|
||||
auto win = ImGui::GetCurrentWindow();
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
const auto lh = std::max(win->DC.CurrLineSize.y, em);
|
||||
const auto rad = em/2 / ImNodes::CanvasState().Zoom;
|
||||
const auto sz = ImVec2(rad*2, lh);
|
||||
const auto pos = ImGui::GetCursorScreenPos() + sz/2;
|
||||
|
||||
auto dlist = ImGui::GetWindowDrawList();
|
||||
dlist->AddCircleFilled(
|
||||
pos, rad, IM_COL32(100, 100, 100, 100));
|
||||
dlist->AddCircleFilled(
|
||||
pos, rad*.8f, IM_COL32(200, 200, 200, 200));
|
||||
|
||||
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
|
@ -1,48 +0,0 @@
|
||||
#include "common/gui_popup.hh"
|
||||
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/util_algorithm.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
|
@ -1,67 +0,0 @@
|
||||
#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_);
|
||||
}
|
||||
|
||||
const char* name() const noexcept { return name_; }
|
||||
|
||||
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
|
@ -1,390 +0,0 @@
|
||||
#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
|
@ -1,206 +0,0 @@
|
||||
#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
|
@ -1,115 +0,0 @@
|
||||
#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
|
@ -1,122 +0,0 @@
|
||||
#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
|
@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/utility/usertype.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
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),
|
||||
shown_(src? src->shown_: false) {
|
||||
}
|
||||
Window(const Window&) = delete;
|
||||
Window(Window&&) = delete;
|
||||
Window& operator=(const Window&) = delete;
|
||||
Window& operator=(Window&&) = delete;
|
||||
|
||||
bool Begin() noexcept {
|
||||
if (std::exchange(set_focus_, false)) {
|
||||
ImGui::SetNextWindowFocus();
|
||||
shown_ = true;
|
||||
}
|
||||
if (!shown_) return false;
|
||||
|
||||
need_end_ = true;
|
||||
return ImGui::Begin(id().c_str(), &shown_);
|
||||
}
|
||||
void End() noexcept {
|
||||
if (need_end_) {
|
||||
ImGui::End();
|
||||
need_end_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetFocus() noexcept {
|
||||
shown_ = true;
|
||||
set_focus_ = true;
|
||||
}
|
||||
|
||||
template <typename Ar>
|
||||
Ar& serialize(Ar& ar) {
|
||||
ar(shown_);
|
||||
return ar;
|
||||
}
|
||||
|
||||
std::string id() const noexcept {
|
||||
return ConcatId(*owner_, title_);
|
||||
}
|
||||
|
||||
bool shownInCurrentFrame() const noexcept {
|
||||
return shown_ || set_focus_;
|
||||
}
|
||||
|
||||
bool shown() const noexcept { return shown_; }
|
||||
bool& shown() noexcept { return shown_; }
|
||||
|
||||
private:
|
||||
File* const owner_;
|
||||
std::string title_;
|
||||
|
||||
bool need_end_ = false;
|
||||
bool set_focus_ = false;
|
||||
|
||||
// persistent params
|
||||
bool shown_;
|
||||
};
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class History {
|
||||
public:
|
||||
class Command;
|
||||
class CorruptException;
|
||||
|
||||
History() = default;
|
||||
virtual ~History() = default;
|
||||
History(const History&) = delete;
|
||||
History(History&&) = delete;
|
||||
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;
|
||||
};
|
||||
|
||||
class History::Command {
|
||||
public:
|
||||
Command() = default;
|
||||
virtual ~Command() = default;
|
||||
Command(const Command&) = delete;
|
||||
Command(Command&&) = delete;
|
||||
Command& operator=(const Command&) = delete;
|
||||
Command& operator=(Command&&) = delete;
|
||||
|
||||
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 {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
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 {
|
||||
std::atomic<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 nf7::ExpiredException {"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
|
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <source_location.hh>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Logger : public File::Interface {
|
||||
public:
|
||||
enum Level {
|
||||
kTrace,
|
||||
kInfo,
|
||||
kWarn,
|
||||
kError,
|
||||
};
|
||||
struct Item;
|
||||
|
||||
Logger() = default;
|
||||
|
||||
// thread-safe
|
||||
virtual void Write(Item&&) noexcept = 0;
|
||||
|
||||
// The parameter is to avoid problems with multi-inheritance and nothing more than.
|
||||
virtual std::shared_ptr<Logger> self(Logger* = nullptr) noexcept = 0;
|
||||
};
|
||||
struct Logger::Item final {
|
||||
public:
|
||||
Item(Level lv, std::string_view m, File::Id f = 0, std::source_location s = std::source_location::current()) noexcept :
|
||||
level(lv), msg(m), file(f), srcloc(s) {
|
||||
}
|
||||
Item(const Item&) = default;
|
||||
Item(Item&&) = default;
|
||||
Item& operator=(const Item&) = default;
|
||||
Item& operator=(Item&&) = default;
|
||||
|
||||
Level level;
|
||||
std::string msg;
|
||||
|
||||
File::Id file;
|
||||
std::source_location srcloc;
|
||||
|
||||
std::exception_ptr ex;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include <source_location.hh>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/file_base.hh"
|
||||
#include "common/logger.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class LoggerRef final : public nf7::FileBase::Feature {
|
||||
public:
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void Trace(std::string_view msg, std::source_location s = std::source_location::current()) noexcept {
|
||||
Write({nf7::Logger::kTrace, msg, 0, s});
|
||||
}
|
||||
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:
|
||||
nf7::File* const file_;
|
||||
const nf7::File::Path path_;
|
||||
|
||||
std::mutex mtx_;
|
||||
File::Id id_;
|
||||
std::shared_ptr<nf7::Logger> logger_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
620
common/luajit.cc
620
common/luajit.cc
@ -1,620 +0,0 @@
|
||||
#include "common/luajit.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "common/logger.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/luajit_thread.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
static void PushStd(lua_State* L) noexcept;
|
||||
|
||||
// buffer <-> lua value conversion
|
||||
template <typename T>
|
||||
static size_t PushArrayFromBytes(
|
||||
lua_State* L, size_t n, const uint8_t* ptr, const uint8_t* end);
|
||||
template <typename T>
|
||||
static size_t PushFromBytes(lua_State* L, const uint8_t* ptr, const uint8_t* end);
|
||||
template <typename T>
|
||||
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")) {
|
||||
PushStd(L);
|
||||
lua_setfield(L, -2, "std");
|
||||
}
|
||||
}
|
||||
void PushImmEnv(lua_State* L) noexcept {
|
||||
if (luaL_newmetatable(L, "nf7::luajit::PushImmEnv")) {
|
||||
lua_createtable(L, 0, 0);
|
||||
PushGlobalTable(L);
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "global is immutable"); });
|
||||
lua_setfield(L, -2, "__newindex");
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PushValue(lua_State* L, const nf7::Value& v) noexcept {
|
||||
new (lua_newuserdata(L, sizeof(v))) nf7::Value(v);
|
||||
|
||||
if (luaL_newmetatable(L, "nf7::Value")) {
|
||||
lua_createtable(L, 0, 0);
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
const auto& v = CheckRef<nf7::Value>(L, 1, "nf7::Value");
|
||||
lua_pushstring(L, v.typeName());
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "type");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
const auto& v = CheckRef<nf7::Value>(L, 1, "nf7::Value");
|
||||
|
||||
struct Visitor final {
|
||||
lua_State* L;
|
||||
const nf7::Value& v;
|
||||
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); }
|
||||
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); }
|
||||
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 { PushVector(L, v.vector()); }
|
||||
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
|
||||
|
||||
auto operator()(Value::Tuple) noexcept {
|
||||
const auto& tup = *v.tuple();
|
||||
lua_createtable(L, 0, 0);
|
||||
size_t arridx = 0;
|
||||
for (auto& p : tup) {
|
||||
PushValue(L, p.second);
|
||||
if (p.first.empty()) {
|
||||
lua_rawseti(L, -2, static_cast<int>(arridx++));
|
||||
} else {
|
||||
lua_setfield(L, -2, p.first.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
v.Visit(Visitor{.L = L, .v = v});
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "value");
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
CheckRef<nf7::Value>(L, 1, "nf7::Value").~Value();
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "__gc");
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
|
||||
assert(v);
|
||||
new (lua_newuserdata(L, sizeof(v))) nf7::Value::ConstVector(v);
|
||||
|
||||
if (luaL_newmetatable(L, "nf7::Value::ConstVector")) {
|
||||
lua_createtable(L, 0, 0);
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
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");
|
||||
}
|
||||
if (offset > static_cast<lua_Integer>(v->size())) {
|
||||
return luaL_error(L, "offset overflow");
|
||||
}
|
||||
|
||||
const uint8_t* ptr = v->data() + offset;
|
||||
const uint8_t* end = v->data() + v->size();
|
||||
|
||||
luaL_checktype(L, 3, LUA_TTABLE);
|
||||
const int ecnt = static_cast<int>(lua_objlen(L, 3));
|
||||
lua_createtable(L, ecnt, 0);
|
||||
|
||||
for (int i = 1; i <= ecnt; ++i) {
|
||||
lua_rawgeti(L, 3, i);
|
||||
if (lua_istable(L, -1)) { // array
|
||||
lua_rawgeti(L, -1, 1);
|
||||
const std::string_view type = luaL_checkstring(L, -1);
|
||||
lua_rawgeti(L, -2, 2);
|
||||
const size_t n = static_cast<size_t>(luaL_checkinteger(L, -1));
|
||||
lua_pop(L, 2);
|
||||
|
||||
if (type == "u8") {
|
||||
ptr += PushArrayFromBytes<uint8_t>(L, n, ptr, end);
|
||||
} else if (type == "u16") {
|
||||
ptr += PushArrayFromBytes<uint16_t>(L, n, ptr, end);
|
||||
} else if (type == "u32") {
|
||||
ptr += PushArrayFromBytes<uint32_t>(L, n, ptr, end);
|
||||
} else if (type == "u64") {
|
||||
ptr += PushArrayFromBytes<uint64_t>(L, n, ptr, end);
|
||||
} else if (type == "s8") {
|
||||
ptr += PushArrayFromBytes<int8_t>(L, n, ptr, end);
|
||||
} else if (type == "s16") {
|
||||
ptr += PushArrayFromBytes<int16_t>(L, n, ptr, end);
|
||||
} else if (type == "s32") {
|
||||
ptr += PushArrayFromBytes<int32_t>(L, n, ptr, end);
|
||||
} else if (type == "s64") {
|
||||
ptr += PushArrayFromBytes<int64_t>(L, n, ptr, end);
|
||||
} else if (type == "f32") {
|
||||
ptr += PushArrayFromBytes<float>(L, n, ptr, end);
|
||||
} else if (type == "f64") {
|
||||
ptr += PushArrayFromBytes<double>(L, n, ptr, end);
|
||||
}
|
||||
} else if (lua_isstring(L, -1)) { // single
|
||||
const std::string_view type = lua_tostring(L, -1);
|
||||
if (type == "u8") {
|
||||
ptr += PushFromBytes<uint8_t>(L, ptr, end);
|
||||
} else if (type == "u16") {
|
||||
ptr += PushFromBytes<uint16_t>(L, ptr, end);
|
||||
} else if (type == "u32") {
|
||||
ptr += PushFromBytes<uint32_t>(L, ptr, end);
|
||||
} else if (type == "u64") {
|
||||
ptr += PushFromBytes<uint64_t>(L, ptr, end);
|
||||
} else if (type == "s8") {
|
||||
ptr += PushFromBytes<int8_t>(L, ptr, end);
|
||||
} else if (type == "s16") {
|
||||
ptr += PushFromBytes<int16_t>(L, ptr, end);
|
||||
} else if (type == "s32") {
|
||||
ptr += PushFromBytes<int32_t>(L, ptr, end);
|
||||
} else if (type == "s64") {
|
||||
ptr += PushFromBytes<int64_t>(L, ptr, end);
|
||||
} else if (type == "f32") {
|
||||
ptr += PushFromBytes<float>(L, ptr, end);
|
||||
} else if (type == "f64") {
|
||||
ptr += PushFromBytes<double>(L, ptr, end);
|
||||
}
|
||||
} else {
|
||||
return luaL_error(L, "unknown type specifier at index: %d", i);
|
||||
}
|
||||
lua_rawseti(L, -3, i);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "get");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
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;
|
||||
});
|
||||
lua_setfield(L, -2, "size");
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
CheckRef<nf7::Value::ConstVector>(L, 1, "nf7::Value::ConstVector").~shared_ptr();
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "__gc");
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
|
||||
new (lua_newuserdata(L, sizeof(v))) std::vector<uint8_t>(std::move(v));
|
||||
|
||||
if (luaL_newmetatable(L, "nf7::Value::MutableVector")) {
|
||||
lua_createtable(L, 0, 0);
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto& v = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
|
||||
const lua_Integer offset = luaL_checkinteger(L, 2);
|
||||
if (offset < 0) return luaL_error(L, "negative offset");
|
||||
|
||||
luaL_checktype(L, 3, LUA_TTABLE);
|
||||
const int len = static_cast<int>(lua_objlen(L, 3));
|
||||
|
||||
uint8_t* ptr = v.data() + offset;
|
||||
uint8_t* end = v.data() + v.size();
|
||||
|
||||
for (int i = 1; i <= len; ++i) {
|
||||
lua_rawgeti(L, 3, i);
|
||||
lua_rawgeti(L, -1, 1);
|
||||
lua_rawgeti(L, -2, 2);
|
||||
|
||||
const std::string_view type = lua_tostring(L, -2);
|
||||
if (type == "u8") {
|
||||
ptr += ToBytes<uint8_t>(L, ptr, end);
|
||||
} else if (type == "u16") {
|
||||
ptr += ToBytes<uint16_t>(L, ptr, end);
|
||||
} else if (type == "u32") {
|
||||
ptr += ToBytes<uint32_t>(L, ptr, end);
|
||||
} else if (type == "u64") {
|
||||
ptr += ToBytes<uint64_t>(L, ptr, end);
|
||||
} else if (type == "s8") {
|
||||
ptr += ToBytes<int8_t>(L, ptr, end);
|
||||
} else if (type == "s16") {
|
||||
ptr += ToBytes<int16_t>(L, ptr, end);
|
||||
} else if (type == "s32") {
|
||||
ptr += ToBytes<int32_t>(L, ptr, end);
|
||||
} else if (type == "s64") {
|
||||
ptr += ToBytes<int64_t>(L, ptr, end);
|
||||
} else if (type == "f32") {
|
||||
ptr += ToBytes<float>(L, ptr, end);
|
||||
} else if (type == "f64") {
|
||||
ptr += ToBytes<double>(L, ptr, end);
|
||||
}
|
||||
lua_pop(L, 3);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "set");
|
||||
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto& v = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
|
||||
const lua_Integer size = luaL_checkinteger(L, 2);
|
||||
if (size < 0) return luaL_error(L, "negative size");
|
||||
v.resize(static_cast<size_t>(size));
|
||||
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) {
|
||||
CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector").~vector();
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "__gc");
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
void PushNodeRootSelectLambda(
|
||||
lua_State* L, const std::shared_ptr<nf7::NodeRootSelectLambda>& la) noexcept {
|
||||
assert(la);
|
||||
|
||||
using T = std::shared_ptr<nf7::NodeRootSelectLambda>;
|
||||
new (lua_newuserdata(L, sizeof(T))) T {la};
|
||||
|
||||
if (luaL_newmetatable(L, "nf7::NodeRootSelectLambda")) {
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
CheckRef<T>(L, 1, "nf7::NodeRootSelectLambda").~T();
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "__gc");
|
||||
}
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
|
||||
std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
|
||||
if (lua_isnoneornil(L, idx)) {
|
||||
return nf7::Value {nf7::Value::Pulse {}};
|
||||
}
|
||||
if (lua_isnumber(L, idx)) {
|
||||
const double n = lua_tonumber(L, idx);
|
||||
return nf7::Value {n};
|
||||
}
|
||||
if (lua_isboolean(L, idx)) {
|
||||
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
|
||||
}
|
||||
if (lua_isstring(L, idx)) {
|
||||
size_t len;
|
||||
const char* str = lua_tolstring(L, idx, &len);
|
||||
return nf7::Value {std::string {str, len}};
|
||||
}
|
||||
if (auto vec = ToVector(L, idx)) {
|
||||
return nf7::Value {std::move(*vec)};
|
||||
}
|
||||
if (auto vec = ToMutableVector(L, idx)) {
|
||||
return nf7::Value {std::move(*vec)};
|
||||
}
|
||||
if (lua_istable(L, idx)) {
|
||||
std::vector<nf7::Value::TuplePair> tup;
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, idx)) {
|
||||
std::string name = "";
|
||||
if (lua_isstring(L, -2)) {
|
||||
name = lua_tostring(L, -2);
|
||||
}
|
||||
auto val = ToValue(L, -1);
|
||||
if (!val) return std::nullopt;
|
||||
tup.push_back({std::move(name), std::move(*val)});
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return nf7::Value {std::move(tup)};
|
||||
}
|
||||
if (auto val = ToRef<nf7::Value>(L, idx, "nf7::Value")) {
|
||||
return *val;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
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;
|
||||
}
|
||||
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexcept {
|
||||
auto ptr = ToRef<std::vector<uint8_t>>(L, idx, "nf7::Value::MutableVector");
|
||||
if (!ptr) return std::nullopt;
|
||||
return std::move(*ptr);
|
||||
}
|
||||
|
||||
|
||||
static void PushStd(lua_State* L) noexcept {
|
||||
lua_newuserdata(L, 0);
|
||||
lua_createtable(L, 0, 0);
|
||||
lua_createtable(L, 0, 0);
|
||||
{
|
||||
// ---- lua lib ----
|
||||
|
||||
// assert(expr[, msg])
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
if (lua_toboolean(L, 1)) {
|
||||
return 0;
|
||||
}
|
||||
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");
|
||||
|
||||
// error(msg)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
return luaL_error(L, luaL_checkstring(L, 1));
|
||||
});
|
||||
lua_setfield(L, -2, "error");
|
||||
|
||||
// load(str)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
if (0 != luaL_loadstring(L, luaL_checkstring(L, 1))) {
|
||||
return luaL_error(L, "lua.load error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "load");
|
||||
|
||||
// pcall(func, args...) -> success, result
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
|
||||
lua_pushboolean(L, true);
|
||||
lua_insert(L, 1);
|
||||
return lua_gettop(L);
|
||||
} else {
|
||||
lua_pushboolean(L, false);
|
||||
lua_insert(L, 1);
|
||||
return 2;
|
||||
}
|
||||
});
|
||||
lua_setfield(L, -2, "pcall");
|
||||
|
||||
|
||||
// ---- math lib ----
|
||||
|
||||
// sin(theta)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "sin");
|
||||
|
||||
// cos(theta)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "cos");
|
||||
|
||||
// tan(slope)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "tan");
|
||||
|
||||
|
||||
// ---- table lib ----
|
||||
|
||||
// meta(table, meta_table)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_checktype(L, 2, LUA_TTABLE);
|
||||
lua_settop(L, 2);
|
||||
lua_setmetatable(L, 1);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "meta");
|
||||
|
||||
|
||||
// ---- time lib ----
|
||||
|
||||
// now()
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
const auto now = nf7::Env::Clock::now().time_since_epoch();
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
|
||||
lua_pushnumber(L, static_cast<double>(ms.count())/1000.);
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "now");
|
||||
|
||||
|
||||
// ---- value lib ----
|
||||
|
||||
// value(entity) -> value
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
if (lua_isstring(L, 2)) {
|
||||
const auto type = std::string_view {lua_tostring(L, 2)};
|
||||
if (type == "integer" || type == "int") {
|
||||
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
|
||||
} else {
|
||||
return luaL_error(L, "unknown type specifier: %s", type);
|
||||
}
|
||||
} else {
|
||||
PushValue(L, CheckValue(L, 1));
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
lua_setfield(L, -2, "value");
|
||||
|
||||
// mvector(vector or mutable vector) -> mutable vector
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
if (auto imm = ToVector(L, 1)) {
|
||||
if (imm->use_count() == 1) {
|
||||
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
|
||||
} else {
|
||||
PushMutableVector(L, std::vector<uint8_t> {**imm});
|
||||
}
|
||||
return 1;
|
||||
} else if (auto mut = ToMutableVector(L, 1)) {
|
||||
PushMutableVector(L, std::vector<uint8_t> {*mut});
|
||||
return 1;
|
||||
} else {
|
||||
PushMutableVector(L, {});
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
lua_setfield(L, -2, "mvector");
|
||||
|
||||
|
||||
// ---- bit manip ----
|
||||
luaL_openlibs(L);
|
||||
luaL_loadstring(L, "return require(\"bit\")");
|
||||
lua_call(L, 0, 1);
|
||||
lua_setfield(L, -2, "bit");
|
||||
}
|
||||
lua_setfield(L, -2, "__index");
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
static size_t PushArrayFromBytes(lua_State* L, size_t n, const uint8_t* ptr, const uint8_t* end) {
|
||||
const size_t size = n*sizeof(T);
|
||||
if (ptr + size > end) {
|
||||
luaL_error(L, "bytes shortage");
|
||||
return 0;
|
||||
}
|
||||
lua_createtable(L, static_cast<int>(n), 0);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(*reinterpret_cast<const T*>(ptr)));
|
||||
} else if constexpr (std::is_floating_point<T>::value) {
|
||||
lua_pushnumber(L, static_cast<lua_Number>(*reinterpret_cast<const T*>(ptr)));
|
||||
} else {
|
||||
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
|
||||
}
|
||||
lua_rawseti(L, -2, static_cast<int>(i + 1));
|
||||
ptr += sizeof(T);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
template <typename T>
|
||||
static size_t PushFromBytes(lua_State* L, const uint8_t* ptr, const uint8_t* end) {
|
||||
const size_t size = sizeof(T);
|
||||
if (ptr + size > end) {
|
||||
luaL_error(L, "bytes shortage");
|
||||
return 0;
|
||||
}
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(*reinterpret_cast<const T*>(ptr)));
|
||||
} else if constexpr (std::is_floating_point<T>::value) {
|
||||
lua_pushnumber(L, static_cast<lua_Number>(*reinterpret_cast<const T*>(ptr)));
|
||||
} else {
|
||||
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
template <typename T>
|
||||
static size_t ToBytes(lua_State* L, uint8_t* ptr, uint8_t* end) {
|
||||
if (lua_istable(L, -1)) {
|
||||
const size_t len = lua_objlen(L, -1);
|
||||
const size_t size = sizeof(T)*len;
|
||||
if (ptr + size > end) {
|
||||
luaL_error(L, "buffer size overflow");
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
lua_rawgeti(L, -1, static_cast<int>(i+1));
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
*reinterpret_cast<T*>(ptr) = static_cast<T>(lua_tointeger(L, -1));
|
||||
} else if constexpr (std::is_floating_point<T>::value) {
|
||||
*reinterpret_cast<T*>(ptr) = static_cast<T>(lua_tonumber(L, -1));
|
||||
} else {
|
||||
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
ptr += sizeof(T);
|
||||
}
|
||||
return size;
|
||||
} else if (lua_isnumber(L, -1)) {
|
||||
if (ptr + sizeof(T) > end) {
|
||||
luaL_error(L, "buffer size overflow");
|
||||
return 0;
|
||||
}
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
*reinterpret_cast<T*>(ptr) = static_cast<T>(lua_tointeger(L, -1));
|
||||
} else if constexpr (std::is_floating_point<T>::value) {
|
||||
*reinterpret_cast<T*>(ptr) = static_cast<T>(lua_tonumber(L, -1));
|
||||
} else {
|
||||
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
|
||||
}
|
||||
return sizeof(T);
|
||||
} else if (lua_isstring(L, -1)) {
|
||||
if constexpr (std::is_same<T, uint8_t>::value) {
|
||||
size_t sz;
|
||||
const char* str = lua_tolstring(L, -1, &sz);
|
||||
std::memcpy(ptr, str, std::min(static_cast<size_t>(end-ptr), sz));
|
||||
return sz;
|
||||
} else {
|
||||
luaL_error(L, "string can be specified for only u8 type");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
luaL_error(L, "number or array expected");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7::luajit
|
140
common/luajit.hh
140
common/luajit.hh
@ -1,140 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "common/node_root_select_lambda.hh"
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
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::ConstVector&) noexcept;
|
||||
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
|
||||
void PushNodeRootSelectLambda(
|
||||
lua_State*, const std::shared_ptr<nf7::NodeRootSelectLambda>&) noexcept;
|
||||
|
||||
std::optional<nf7::Value> ToValue(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;
|
||||
|
||||
|
||||
template <typename T>
|
||||
void Push(lua_State* L, T v) noexcept {
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
lua_pushinteger(L, static_cast<lua_Integer>(v));
|
||||
} else if constexpr (std::is_floating_point<T>::value) {
|
||||
lua_pushnumber(L, static_cast<lua_Number>(v));
|
||||
} else if constexpr (std::is_null_pointer<T>::value) {
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
|
||||
}
|
||||
}
|
||||
inline void Push(lua_State* L, const std::string& v) noexcept {
|
||||
lua_pushstring(L, v.c_str());
|
||||
}
|
||||
inline void Push(lua_State* L, const Value& v) noexcept {
|
||||
luajit::PushValue(L, v);
|
||||
}
|
||||
inline void Push(lua_State* L, const nf7::Value::Vector& v) noexcept {
|
||||
luajit::PushVector(L, v);
|
||||
}
|
||||
inline void Push(lua_State* L, const std::vector<uint8_t>& v) noexcept {
|
||||
luajit::PushMutableVector(L, std::vector<uint8_t> {v});
|
||||
}
|
||||
inline void Push(lua_State* L, std::vector<uint8_t>&& v) noexcept {
|
||||
luajit::PushMutableVector(L, std::move(v));
|
||||
}
|
||||
inline void Push(lua_State* L, const std::shared_ptr<nf7::NodeRootSelectLambda>& la) noexcept {
|
||||
luajit::PushNodeRootSelectLambda(L, la);
|
||||
}
|
||||
|
||||
inline int PushAll(lua_State*) noexcept {
|
||||
return 0;
|
||||
}
|
||||
template <typename T, typename... Args>
|
||||
int PushAll(lua_State* L, T v, Args&&... args) noexcept {
|
||||
if constexpr (std::is_reference<T>::value) {
|
||||
Push(L, std::forward<T>(v));
|
||||
} else {
|
||||
Push(L, v);
|
||||
}
|
||||
return 1+PushAll(L, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline void PushWeakPtr(lua_State* L, const std::weak_ptr<T>& wptr) noexcept {
|
||||
new (lua_newuserdata(L, sizeof(wptr))) std::weak_ptr<T>(wptr);
|
||||
}
|
||||
template <typename T>
|
||||
inline void PushWeakPtrDeleter(lua_State* L, const std::weak_ptr<T>& = {}) noexcept {
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
reinterpret_cast<std::weak_ptr<T>*>(lua_touserdata(L, 1))->~weak_ptr();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
inline bool MatchMetaName(lua_State* L, int idx, const char* type) noexcept {
|
||||
if (0 == lua_getmetatable(L, idx)) {
|
||||
return false;
|
||||
}
|
||||
luaL_getmetatable(L, type);
|
||||
const bool ret = lua_rawequal(L, -1, -2);
|
||||
lua_pop(L, 2);
|
||||
return ret;
|
||||
}
|
||||
template <typename T>
|
||||
inline T* ToRef(lua_State* L, int idx, const char* type) noexcept {
|
||||
return MatchMetaName(L, idx, type)? reinterpret_cast<T*>(lua_touserdata(L, idx)): nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::shared_ptr<T> CheckWeakPtr(lua_State* L, int idx, const char* type) {
|
||||
auto ptr = reinterpret_cast<std::weak_ptr<T>*>(luaL_checkudata(L, idx, type));
|
||||
if (auto ret = ptr->lock()) {
|
||||
return ret;
|
||||
} else {
|
||||
luaL_error(L, "object expired: %s", typeid(T).name());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
inline T& CheckRef(lua_State* L, int idx, const char* type) {
|
||||
return *reinterpret_cast<T*>(luaL_checkudata(L, idx, type));
|
||||
}
|
||||
inline const std::shared_ptr<nf7::NodeRootSelectLambda>& CheckNodeRootSelectLambda(
|
||||
lua_State* L, int idx) {
|
||||
return CheckRef<std::shared_ptr<nf7::NodeRootSelectLambda>>(
|
||||
L, idx, "nf7::NodeRootSelectLambda");
|
||||
}
|
||||
inline nf7::Value CheckValue(lua_State* L, int idx) {
|
||||
auto v = ToValue(L, idx);
|
||||
if (!v) luaL_error(L, "expected nf7::Value");
|
||||
return std::move(*v);
|
||||
}
|
||||
|
||||
inline void ToStringList(lua_State* L, std::vector<std::string>& v, int idx) {
|
||||
const size_t n = lua_objlen(L, idx);
|
||||
v.clear();
|
||||
v.reserve(n);
|
||||
for (int i = 1; i <= static_cast<int>(n); ++i) {
|
||||
lua_rawgeti(L, idx, i);
|
||||
if (auto str = lua_tostring(L, -1)) {
|
||||
v.push_back(str);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
class Queue : public File::Interface {
|
||||
public:
|
||||
using Task = std::function<void(lua_State*)>;
|
||||
|
||||
static constexpr auto kPath = "$/_luajit";
|
||||
|
||||
Queue() = default;
|
||||
Queue(const Queue&) = delete;
|
||||
Queue(Queue&&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
Queue& operator=(Queue&&) = delete;
|
||||
|
||||
// thread-safe
|
||||
virtual void Push(
|
||||
const std::shared_ptr<nf7::Context>&, Task&&, nf7::Env::Time t = {}) noexcept = 0;
|
||||
|
||||
virtual std::shared_ptr<Queue> self() noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/luajit_queue.hh"
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
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); });
|
||||
}
|
||||
Ref(const Ref&) = delete;
|
||||
Ref(Ref&&) = delete;
|
||||
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_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<nf7::Context> ctx_;
|
||||
std::shared_ptr<nf7::luajit::Queue> q_;
|
||||
int idx_;
|
||||
};
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,286 +0,0 @@
|
||||
#include "common/luajit_thread.hh"
|
||||
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "common/node.hh"
|
||||
#include "common/node_root_select_lambda.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
constexpr size_t kInstructionLimit = 10000000;
|
||||
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;
|
||||
|
||||
|
||||
lua_State* Thread::Init(lua_State* L) noexcept {
|
||||
assert(state_ == kInitial);
|
||||
|
||||
th_ = lua_newthread(L);
|
||||
th_ref_.emplace(ctx_, ljq_, L);
|
||||
|
||||
state_ = kPaused;
|
||||
return th_;
|
||||
}
|
||||
void Thread::Resume(lua_State* L, int narg) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
|
||||
if (state_ == kAborted) return;
|
||||
assert(L == th_);
|
||||
assert(state_ == kPaused);
|
||||
|
||||
static const auto kHook = [](auto L, auto) {
|
||||
luaL_error(L, "reached instruction limit (<=1e7)");
|
||||
};
|
||||
lua_sethook(L, kHook, LUA_MASKCOUNT, kInstructionLimit);
|
||||
|
||||
// set global table
|
||||
PushGlobalTable(L);
|
||||
PushWeakPtr(L, weak_from_this());
|
||||
PushMeta(L);
|
||||
lua_setmetatable(L, -2);
|
||||
lua_setfield(L, -2, "nf7");
|
||||
lua_pop(L, 1);
|
||||
|
||||
state_ = kRunning;
|
||||
k.unlock();
|
||||
active_ = true;
|
||||
const auto ret = lua_resume(L, narg);
|
||||
active_ = false;
|
||||
k.lock();
|
||||
if (state_ == kAborted) return;
|
||||
switch (ret) {
|
||||
case 0:
|
||||
state_ = kFinished;
|
||||
break;
|
||||
case LUA_YIELD:
|
||||
state_ = kPaused;
|
||||
break;
|
||||
default:
|
||||
state_ = kAborted;
|
||||
}
|
||||
if (!std::exchange(skip_handle_, false)) {
|
||||
handler_(*this, L);
|
||||
}
|
||||
}
|
||||
void Thread::Abort() noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
state_ = kAborted;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
lua_setfield(L, -2, "__gc");
|
||||
|
||||
lua_createtable(L, 0, 0);
|
||||
{
|
||||
// nf7:resolve(path)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
auto base = th->ctx()->initiator();
|
||||
|
||||
std::string path = luaL_checkstring(L, 2);
|
||||
th->env().ExecSub(th->ctx(), [th, L, base, path = std::move(path)]() {
|
||||
try {
|
||||
th->ExecResume(L, th->env().GetFileOrThrow(base).ResolveOrThrow(path).id());
|
||||
} catch (nf7::File::NotFoundException&) {
|
||||
th->ExecResume(L, 0);
|
||||
}
|
||||
});
|
||||
th->ExpectYield(L);
|
||||
return lua_yield(L, 0);
|
||||
});
|
||||
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);
|
||||
|
||||
const auto id = luaL_checkinteger(L, 2);
|
||||
std::string iface = luaL_checkstring(L, 3);
|
||||
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 == "node") {
|
||||
th->ExecResume(
|
||||
L, nf7::NodeRootSelectLambda::Create(
|
||||
th->ctx(), f.template interfaceOrThrow<nf7::Node>()));
|
||||
} else {
|
||||
throw nf7::Exception {"unknown interface: "+iface};
|
||||
}
|
||||
} catch (nf7::Exception& e) {
|
||||
th->ExecResume(L, nullptr, e.msg());
|
||||
}
|
||||
});
|
||||
th->ExpectYield(L);
|
||||
return lua_yield(L, 0);
|
||||
});
|
||||
lua_setfield(L, -2, "query");
|
||||
|
||||
// nf7:sleep(sec)
|
||||
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:send(obj, params...)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
auto la = luajit::CheckNodeRootSelectLambda(L, 2);
|
||||
la->ExecSend(luaL_checkstring(L, 3), luajit::CheckValue(L, 4));
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(L, -2, "send");
|
||||
|
||||
// nf7:recv(obj, params...)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
auto la = luajit::CheckNodeRootSelectLambda(L, 2);
|
||||
|
||||
std::unordered_set<std::string> names;
|
||||
if (lua_istable(L, 3)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 3)) {
|
||||
if (lua_isstring(L, -1)) {
|
||||
names.insert(lua_tostring(L, -1));
|
||||
} else {
|
||||
return luaL_error(L, "table contains non-string value");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
for (int i = 3; i <= lua_gettop(L); ++i) {
|
||||
names.insert(luaL_checkstring(L, i));
|
||||
}
|
||||
}
|
||||
|
||||
auto fu = la->Select(std::move(names));
|
||||
if (fu.done()) {
|
||||
try {
|
||||
const auto& p = fu.value();
|
||||
lua_pushstring(L, p.first.c_str());
|
||||
luajit::PushValue(L, p.second);
|
||||
return 2;
|
||||
} catch (nf7::Exception& e) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
fu.ThenIf([L, th](auto& p) {
|
||||
th->ExecResume(L, p.first, p.second);
|
||||
}).template Catch<nf7::Exception>(nullptr, [L, th](nf7::Exception&) {
|
||||
th->ExecResume(L);
|
||||
});
|
||||
th->ExpectYield(L);
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
});
|
||||
lua_setfield(L, -2, "recv");
|
||||
|
||||
// nf7:yield(results...)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
return lua_yield(L, lua_gettop(L)-1);
|
||||
});
|
||||
lua_setfield(L, -2, "yield");
|
||||
|
||||
// logging functions
|
||||
static const auto log_write = [](lua_State* L, nf7::Logger::Level lv) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
auto logger = th->logger();
|
||||
if (!logger) return luaL_error(L, "logger is not installed on current thread");
|
||||
|
||||
const int n = lua_gettop(L);
|
||||
std::stringstream st;
|
||||
for (int i = 2; i <= n; ++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;
|
||||
};
|
||||
lua_pushcfunction(L, [](auto L) { return log_write(L, nf7::Logger::kTrace); });
|
||||
lua_setfield(L, -2, "trace");
|
||||
lua_pushcfunction(L, [](auto L) { return log_write(L, nf7::Logger::kInfo); });
|
||||
lua_setfield(L, -2, "info");
|
||||
lua_pushcfunction(L, [](auto L) { return log_write(L, nf7::Logger::kWarn); });
|
||||
lua_setfield(L, -2, "warn");
|
||||
lua_pushcfunction(L, [](auto L) { return log_write(L, nf7::Logger::kError); });
|
||||
lua_setfield(L, -2, "error");
|
||||
}
|
||||
lua_setfield(L, -2, "__index");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,159 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/future.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/luajit.hh"
|
||||
#include "common/luajit_ref.hh"
|
||||
#include "common/node.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
public:
|
||||
static constexpr const char* kTypeName = "nf7::luajit::Thread";
|
||||
|
||||
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
|
||||
using Handler = std::function<void(Thread&, lua_State*)>;
|
||||
|
||||
class Lambda;
|
||||
|
||||
class Exception final : public nf7::Exception {
|
||||
public:
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
// Creates a handler to finalize a promise.
|
||||
template <typename T>
|
||||
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);
|
||||
th->EnsureActive(L);
|
||||
return th;
|
||||
}
|
||||
|
||||
Thread() = delete;
|
||||
Thread(const std::shared_ptr<nf7::Context>& ctx,
|
||||
const std::shared_ptr<nf7::luajit::Queue>& ljq,
|
||||
Handler&& handler) noexcept :
|
||||
ctx_(ctx), ljq_(ljq), handler_(std::move(handler)) {
|
||||
}
|
||||
Thread(const Thread&) = delete;
|
||||
Thread(Thread&&) = delete;
|
||||
Thread& operator=(const Thread&) = delete;
|
||||
Thread& operator=(Thread&&) = delete;
|
||||
|
||||
void Install(const std::shared_ptr<nf7::LoggerRef>& logger) noexcept {
|
||||
assert(state_ == kInitial);
|
||||
logger_ = logger;
|
||||
}
|
||||
|
||||
// must be called on luajit thread
|
||||
lua_State* Init(lua_State* L) noexcept;
|
||||
|
||||
// must be called on luajit thread
|
||||
// L must be a thread state, which is returned by Init().
|
||||
void Resume(lua_State* L, int narg) noexcept;
|
||||
|
||||
// must be called on luajit thread
|
||||
// handler_ won't be called on next yielding
|
||||
void ExpectYield(lua_State*) noexcept {
|
||||
skip_handle_ = true;
|
||||
}
|
||||
|
||||
// must be called on luajit thread
|
||||
void EnsureActive(lua_State* L) {
|
||||
if (!active_) {
|
||||
luaL_error(L, "thread is not active");
|
||||
}
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void Abort() noexcept;
|
||||
|
||||
// queue a task that exec Resume()
|
||||
// thread-safe
|
||||
template <typename... Args>
|
||||
void ExecResume(lua_State* L, Args&&... args) noexcept {
|
||||
auto self = shared_from_this();
|
||||
ljq_->Push(ctx_, [this, L, self, args...](auto) mutable {
|
||||
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
|
||||
});
|
||||
}
|
||||
|
||||
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::LoggerRef>& logger() const noexcept { return logger_; }
|
||||
State state() const noexcept { return state_; }
|
||||
|
||||
private:
|
||||
// initialized by constructor
|
||||
std::mutex mtx_;
|
||||
|
||||
std::shared_ptr<nf7::Context> ctx_;
|
||||
std::shared_ptr<nf7::luajit::Queue> ljq_;
|
||||
|
||||
Handler handler_;
|
||||
std::atomic<State> state_ = kInitial;
|
||||
|
||||
|
||||
// initialized on Init()
|
||||
lua_State* th_ = nullptr;
|
||||
std::optional<nf7::luajit::Ref> th_ref_;
|
||||
|
||||
|
||||
// installed features
|
||||
std::shared_ptr<nf7::LoggerRef> logger_;
|
||||
|
||||
|
||||
// mutable params
|
||||
bool active_ = false; // true while executing lua_resume
|
||||
bool skip_handle_ = false; // handler_ won't be called on next yield
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
Thread::Handler Thread::CreatePromiseHandler(
|
||||
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
|
||||
return [pro = pro, f = std::move(f)](auto& self, auto L) mutable {
|
||||
switch (self.state()) {
|
||||
case kPaused:
|
||||
pro.template Throw<nf7::Exception>("unexpected yield");
|
||||
break;
|
||||
case kFinished:
|
||||
pro.Wrap([&]() { return f(L); });
|
||||
break;
|
||||
case kAborted:
|
||||
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
throw 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,197 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/luajit_thread.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/luajit.hh"
|
||||
#include "common/node.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
class Thread::Lambda final : public Thread::RegistryItem,
|
||||
public std::enable_shared_from_this<Thread::Lambda> {
|
||||
public:
|
||||
static constexpr const char* kTypeName = "nf7::luajit::Thread::Lambda";
|
||||
|
||||
static void CreateAndPush(
|
||||
lua_State* L, const std::shared_ptr<Thread>& th, nf7::File& f) {
|
||||
auto la = std::make_shared<Thread::Lambda>(th, f.interfaceOrThrow<nf7::Node>());
|
||||
th->ljq()->Push(th->ctx(), [L, th, la](auto) {
|
||||
th->Register(L, la);
|
||||
la->Push(L);
|
||||
th->Resume(L, 1);
|
||||
});
|
||||
}
|
||||
|
||||
static std::shared_ptr<Thread::Lambda> GetPtr(lua_State* L, int idx) {
|
||||
auto self = luajit::CheckWeakPtr<Thread::Lambda>(L, idx, kTypeName);
|
||||
self->GetThread(L)->EnsureActive(L);
|
||||
return self;
|
||||
}
|
||||
|
||||
// must be created on main thread
|
||||
explicit Lambda(const std::shared_ptr<Thread>& th, nf7::Node& n) noexcept;
|
||||
|
||||
void Push(lua_State* L) noexcept {
|
||||
luajit::PushWeakPtr<Thread::Lambda>(L, shared_from_this());
|
||||
PushMeta(L);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<Thread> th_;
|
||||
|
||||
class Receiver;
|
||||
std::shared_ptr<Receiver> recv_;
|
||||
std::shared_ptr<Node::Lambda> la_;
|
||||
|
||||
|
||||
std::shared_ptr<Thread> GetThread(lua_State* L) {
|
||||
if (auto th = th_.lock()) {
|
||||
return th;
|
||||
} else {
|
||||
luaL_error(L, "thread expired");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void PushMeta(lua_State* L) noexcept;
|
||||
};
|
||||
|
||||
|
||||
// Receives an output from targetted lambda and Resumes the Thread.
|
||||
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(nf7::Env& env, nf7::File::Id id) noexcept :
|
||||
Node::Lambda(env, id, nullptr) {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
ResumeIf();
|
||||
}
|
||||
|
||||
// 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<std::string>&& names) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
L_ = L;
|
||||
th_ = th;
|
||||
waiting_ = std::move(names);
|
||||
return ResumeIf(false);
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<std::pair<std::string, Value>> values_;
|
||||
|
||||
std::mutex mtx_;
|
||||
lua_State* L_;
|
||||
std::shared_ptr<Thread> th_;
|
||||
std::vector<std::string> waiting_;
|
||||
|
||||
|
||||
// don't forget to lock mtx_
|
||||
bool ResumeIf(bool yielded = true) noexcept;
|
||||
};
|
||||
|
||||
|
||||
Thread::Lambda::Lambda(const std::shared_ptr<Thread>& th, nf7::Node& n) noexcept :
|
||||
th_(th),
|
||||
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)) {
|
||||
lua_createtable(L, 0, 0);
|
||||
|
||||
// Lambda:send(name or idx, value)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto self = GetPtr(L, 1);
|
||||
|
||||
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, name = std::move(name), val = std::move(val)]() mutable {
|
||||
self->la_->Handle(name, std::move(val), self->recv_);
|
||||
th->ExecResume(L);
|
||||
});
|
||||
|
||||
th->ExpectYield(L);
|
||||
return lua_yield(L, 0);
|
||||
});
|
||||
lua_setfield(L, -2, "send");
|
||||
|
||||
// Lambda:recv(handler)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto self = GetPtr(L, 1);
|
||||
|
||||
std::vector<std::string> names = {};
|
||||
if (lua_istable(L, 2)) {
|
||||
names.resize(lua_objlen(L, 2));
|
||||
for (size_t i = 0; i < names.size(); ++i) {
|
||||
lua_rawgeti(L, 2, static_cast<int>(i+1));
|
||||
names[i] = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
names.push_back(lua_tostring(L, 2));
|
||||
}
|
||||
|
||||
auto th = self->GetThread(L);
|
||||
if (self->recv_->Select(L, th, std::move(names))) {
|
||||
return 2;
|
||||
} else {
|
||||
th->ExpectYield(L);
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
});
|
||||
lua_setfield(L, -2, "recv");
|
||||
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
PushWeakPtrDeleter<Thread::Lambda>(L);
|
||||
lua_setfield(L, -2, "__gc");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Thread::Lambda::Receiver::ResumeIf(bool yielded) noexcept {
|
||||
if (!th_) return false;
|
||||
|
||||
for (auto p = values_.begin(); p < values_.end(); ++p) {
|
||||
auto itr = std::find(waiting_.begin(), waiting_.end(), p->first);
|
||||
if (itr == waiting_.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (yielded) {
|
||||
th_->ExecResume(L_, *itr, p->second);
|
||||
} else {
|
||||
luajit::PushAll(L_, *itr, p->second);
|
||||
}
|
||||
values_.erase(p);
|
||||
waiting_ = {};
|
||||
th_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Memento : public File::Interface {
|
||||
public:
|
||||
class Tag;
|
||||
class CorruptException;
|
||||
|
||||
Memento() = default;
|
||||
Memento(const Memento&) = delete;
|
||||
Memento(Memento&&) = delete;
|
||||
Memento& operator=(const Memento&) = delete;
|
||||
Memento& operator=(Memento&&) = delete;
|
||||
|
||||
virtual std::shared_ptr<Tag> Save() noexcept = 0;
|
||||
virtual void Restore(const std::shared_ptr<Tag>&) = 0;
|
||||
};
|
||||
|
||||
class Memento::Tag {
|
||||
public:
|
||||
using Id = uint64_t;
|
||||
|
||||
Tag() = delete;
|
||||
Tag(Id id) noexcept : id_(id) {
|
||||
}
|
||||
virtual ~Tag() = default;
|
||||
Tag(const Tag&) = default;
|
||||
Tag(Tag&&) = default;
|
||||
Tag& operator=(const Tag&) = delete;
|
||||
Tag& operator=(Tag&&) = delete;
|
||||
|
||||
Id id() const noexcept { return id_; }
|
||||
|
||||
private:
|
||||
Id id_;
|
||||
};
|
||||
|
||||
class Memento::CorruptException : public Exception {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,58 +0,0 @@
|
||||
#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
|
@ -1,20 +0,0 @@
|
||||
#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
|
109
common/mutex.hh
109
common/mutex.hh
@ -1,109 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "common/future.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
// nf7::Mutex is not thread-safe.
|
||||
class Mutex final {
|
||||
public:
|
||||
class Lock;
|
||||
|
||||
Mutex() noexcept {
|
||||
}
|
||||
~Mutex() noexcept;
|
||||
|
||||
nf7::Future<std::shared_ptr<Lock>> AcquireLock(bool ex = false) noexcept {
|
||||
if (auto ret = TryAcquireLock(ex)) {
|
||||
return {ret};
|
||||
} else {
|
||||
if (ex || pends_.size() == 0 || pends_.back().ex) {
|
||||
pends_.push_back({.pro = {}, .ex = ex});
|
||||
}
|
||||
return pends_.back().pro.future();
|
||||
}
|
||||
}
|
||||
std::shared_ptr<Lock> TryAcquireLock(bool ex = false) noexcept {
|
||||
auto k = TryAcquireLock_(ex);
|
||||
if (k) {
|
||||
onLock();
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
std::function<void()> onLock = [](){};
|
||||
std::function<void()> onUnlock = [](){};
|
||||
|
||||
private:
|
||||
bool ex_ = false;
|
||||
std::weak_ptr<Lock> k_;
|
||||
|
||||
struct Item final {
|
||||
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
|
||||
bool ex;
|
||||
};
|
||||
std::deque<Item> pends_;
|
||||
|
||||
|
||||
std::shared_ptr<Lock> TryAcquireLock_(bool ex) noexcept {
|
||||
if (auto k = k_.lock()) {
|
||||
if (!ex_ && !ex) {
|
||||
return k;
|
||||
}
|
||||
} else {
|
||||
k = std::make_shared<Lock>(*this);
|
||||
ex_ = ex;
|
||||
k_ = k;
|
||||
return k;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class Mutex::Lock final {
|
||||
public:
|
||||
friend nf7::Mutex;
|
||||
|
||||
Lock() = delete;
|
||||
Lock(nf7::Mutex& mtx) noexcept : mtx_(&mtx) {
|
||||
}
|
||||
Lock(const Lock&) = delete;
|
||||
Lock(Lock&&) = delete;
|
||||
Lock& operator=(const Lock&) = delete;
|
||||
Lock& operator=(Lock&&) = delete;
|
||||
|
||||
~Lock() noexcept {
|
||||
if (mtx_) {
|
||||
auto& pends = mtx_->pends_;
|
||||
if (pends.size() > 0) {
|
||||
auto item = std::move(pends.front());
|
||||
pends.pop_front();
|
||||
mtx_->ex_ = false;
|
||||
mtx_->k_ = {};
|
||||
|
||||
auto k = mtx_->TryAcquireLock_(item.ex);
|
||||
assert(k);
|
||||
item.pro.Return(std::move(k));
|
||||
} else {
|
||||
mtx_->onUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Mutex* mtx_;
|
||||
};
|
||||
Mutex::~Mutex() noexcept {
|
||||
pends_.clear();
|
||||
if (auto k = k_.lock()) {
|
||||
k->mtx_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class NFile final {
|
||||
public:
|
||||
class Exception final : public nf7::Exception {
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
enum Flag : uint8_t {
|
||||
kRead = 1 << 0,
|
||||
kWrite = 1 << 1,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
NFile() = delete;
|
||||
NFile(const std::filesystem::path& path, Flags flags) :
|
||||
path_(path), flags_(flags) {
|
||||
Init();
|
||||
}
|
||||
~NFile() noexcept;
|
||||
NFile(const NFile&) = delete;
|
||||
NFile(NFile&&) = delete;
|
||||
NFile& operator=(const NFile&) = delete;
|
||||
NFile& operator=(NFile&&) = delete;
|
||||
|
||||
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
||||
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
||||
size_t Truncate(size_t size);
|
||||
|
||||
Flags flags() const noexcept {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::filesystem::path path_;
|
||||
const Flags flags_;
|
||||
|
||||
uintptr_t handle_;
|
||||
|
||||
void Init();
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,69 +0,0 @@
|
||||
#include "common/nfile.hh"
|
||||
|
||||
extern "C" {
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
}
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
void NFile::Init() {
|
||||
int flags = 0;
|
||||
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
||||
flags |= O_RDWR | O_CREAT;
|
||||
} else if (flags_ & kRead) {
|
||||
flags |= O_RDONLY;
|
||||
} else if (flags_ & kWrite) {
|
||||
flags |= O_WRONLY | O_CREAT;
|
||||
}
|
||||
|
||||
int fd = open(path_.string().c_str(), flags, 0600);
|
||||
if (fd < 0) {
|
||||
throw NFile::Exception {"open failure"};
|
||||
}
|
||||
handle_ = static_cast<uint64_t>(fd);
|
||||
}
|
||||
NFile::~NFile() noexcept {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
if (close(fd) == -1) {
|
||||
// ;(
|
||||
}
|
||||
}
|
||||
|
||||
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
const auto off = static_cast<off_t>(offset);
|
||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||
throw NFile::Exception {"lseek failure"};
|
||||
}
|
||||
const auto ret = read(fd, buf, size);
|
||||
if (ret == -1) {
|
||||
throw NFile::Exception {"read failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
const auto off = static_cast<off_t>(offset);
|
||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||
throw nf7::NFile::Exception {"lseek failure"};
|
||||
}
|
||||
const auto ret = write(fd, buf, size);
|
||||
if (ret == -1) {
|
||||
throw nf7::NFile::Exception {"write failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Truncate(size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
||||
throw nf7::NFile::Exception {"ftruncate failure"};
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "common/file_base.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class NFileWatcher final : public nf7::FileBase::Feature {
|
||||
public:
|
||||
NFileWatcher() = default;
|
||||
NFileWatcher(const NFileWatcher&) = delete;
|
||||
NFileWatcher(NFileWatcher&&) = delete;
|
||||
NFileWatcher& operator=(const NFileWatcher&) = delete;
|
||||
NFileWatcher& operator=(NFileWatcher&&) = delete;
|
||||
|
||||
void Watch(const std::filesystem::path& npath) noexcept {
|
||||
npath_ = npath;
|
||||
lastmod_ = std::nullopt;
|
||||
}
|
||||
|
||||
std::function<void()> onMod;
|
||||
|
||||
protected:
|
||||
void Update() noexcept override
|
||||
try {
|
||||
if (npath_) {
|
||||
const auto lastmod = std::filesystem::last_write_time(*npath_);
|
||||
if (lastmod_ && lastmod > *lastmod_) {
|
||||
onMod();
|
||||
}
|
||||
lastmod_ = lastmod;
|
||||
}
|
||||
} catch (std::filesystem::filesystem_error&) {
|
||||
lastmod_.emplace();
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<std::filesystem::path> npath_;
|
||||
std::optional<std::filesystem::file_time_type> lastmod_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,80 +0,0 @@
|
||||
#include "common/nfile.hh"
|
||||
|
||||
extern "C" {
|
||||
#include <windows.h>
|
||||
}
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
void NFile::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 NFile::Exception {"open failure"};
|
||||
}
|
||||
handle_ = reinterpret_cast<uintptr_t>(h);
|
||||
}
|
||||
NFile::~NFile() noexcept {
|
||||
auto h = reinterpret_cast<HANDLE>(handle_);
|
||||
if (!CloseHandle(h)) {
|
||||
// ;(
|
||||
}
|
||||
}
|
||||
|
||||
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||
|
||||
LONG off_low = offset & 0xFFFFFFFF;
|
||||
LONG off_high = offset >> 32;
|
||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||
throw NFile::Exception {"failed to set file pointer"};
|
||||
}
|
||||
DWORD ret;
|
||||
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
||||
throw NFile::Exception {"read failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||
|
||||
LONG off_low = offset & 0xFFFFFFFF;
|
||||
LONG off_high = offset >> 32;
|
||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||
throw NFile::Exception {"failed to set file pointer"};
|
||||
}
|
||||
DWORD ret;
|
||||
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
|
||||
throw NFile::Exception {"read failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Truncate(size_t size) {
|
||||
const auto h = reinterpret_cast<HANDLE>(handle_);
|
||||
|
||||
LONG off_low = size & 0xFFFFFFFF;
|
||||
LONG off_high = size >> 32;
|
||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
|
||||
throw NFile::Exception {"failed to set file pointer"};
|
||||
}
|
||||
if (!SetEndOfFile(h)) {
|
||||
throw NFile::Exception {"SetEndOfFile failure"};
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,94 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Node : public File::Interface {
|
||||
public:
|
||||
class Editor;
|
||||
class Lambda;
|
||||
|
||||
enum Flag : uint8_t {
|
||||
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) noexcept : flags_(f) { }
|
||||
Node(const Node&) = default;
|
||||
Node(Node&&) = default;
|
||||
Node& operator=(const Node&) = default;
|
||||
Node& operator=(Node&&) = default;
|
||||
|
||||
virtual std::shared_ptr<Lambda> CreateLambda(const std::shared_ptr<Lambda>&) noexcept = 0;
|
||||
|
||||
virtual void UpdateNode(Editor&) noexcept { }
|
||||
virtual void UpdateMenu(Editor&) noexcept { }
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
class Node::Editor {
|
||||
public:
|
||||
Editor() = default;
|
||||
virtual ~Editor() = default;
|
||||
Editor(const Editor&) = delete;
|
||||
Editor(Editor&&) = delete;
|
||||
Editor& operator=(const Editor&) = delete;
|
||||
Editor& operator=(Editor&&) = delete;
|
||||
|
||||
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;
|
||||
virtual void RemoveLink(Node& src_node, std::string_view src_name,
|
||||
Node& dst_node, std::string_view dst_name) noexcept = 0;
|
||||
|
||||
virtual std::vector<std::pair<Node*, std::string>> GetSrcOf(Node&, std::string_view) const noexcept = 0;
|
||||
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
|
@ -1,120 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
#include <yas/types/utility/usertype.hpp>
|
||||
|
||||
#include "common/aggregate_command.hh"
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class NodeLinkStore {
|
||||
public:
|
||||
class SwapCommand;
|
||||
|
||||
struct Link {
|
||||
public:
|
||||
uint64_t src_id;
|
||||
std::string src_name;
|
||||
uint64_t dst_id;
|
||||
std::string dst_name;
|
||||
|
||||
bool operator==(const Link& other) const noexcept {
|
||||
if (src_id && other.src_id && src_id != other.src_id) return false;
|
||||
if (dst_id && other.dst_id && dst_id != other.dst_id) return false;
|
||||
if (src_name.size() && other.src_name.size() && src_name != other.src_name) return false;
|
||||
if (dst_name.size() && other.dst_name.size() && dst_name != other.dst_name) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Ar>
|
||||
Ar& serialize(Ar& ar) {
|
||||
ar(src_id, src_name, dst_id, dst_name);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
NodeLinkStore() = default;
|
||||
NodeLinkStore(const NodeLinkStore&) = default;
|
||||
NodeLinkStore(NodeLinkStore&&) = default;
|
||||
NodeLinkStore& operator=(const NodeLinkStore&) = default;
|
||||
NodeLinkStore& operator=(NodeLinkStore&&) = default;
|
||||
|
||||
template <typename Ar>
|
||||
Ar& serialize(Ar& ar) {
|
||||
ar(links_);
|
||||
return ar;
|
||||
}
|
||||
|
||||
void AddLink(Link&& lk) noexcept {
|
||||
links_.push_back(std::move(lk));
|
||||
}
|
||||
void RemoveLink(const Link& lk) noexcept {
|
||||
links_.erase(std::remove(links_.begin(), links_.end(), lk), links_.end());
|
||||
}
|
||||
|
||||
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_; }
|
||||
|
||||
private:
|
||||
std::vector<Link> links_;
|
||||
};
|
||||
|
||||
class NodeLinkStore::SwapCommand : public History::Command {
|
||||
public:
|
||||
static std::unique_ptr<SwapCommand> CreateToAdd(
|
||||
NodeLinkStore& target, Link&& lk) noexcept {
|
||||
return std::make_unique<SwapCommand>(target, std::move(lk), false);
|
||||
}
|
||||
static std::unique_ptr<SwapCommand> CreateToRemove(
|
||||
NodeLinkStore& target, Link&& lk) noexcept {
|
||||
return std::make_unique<SwapCommand>(target, std::move(lk), true);
|
||||
}
|
||||
|
||||
SwapCommand(NodeLinkStore& target, Link&& lk, bool added) noexcept :
|
||||
target_(&target), link_(std::move(lk)), added_(added) {
|
||||
}
|
||||
|
||||
void Apply() noexcept override { Exec(); }
|
||||
void Revert() noexcept override { Exec(); }
|
||||
|
||||
private:
|
||||
NodeLinkStore* const target_;
|
||||
Link link_;
|
||||
bool added_;
|
||||
|
||||
void Exec() noexcept {
|
||||
added_?
|
||||
target_->RemoveLink(link_):
|
||||
target_->AddLink(Link(link_));
|
||||
added_ = !added_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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<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()) ||
|
||||
(lk.dst_id == id && std::find(in .begin(), in .end(), lk.dst_name) == in .end());
|
||||
if (rm) cmds.push_back(SwapCommand::CreateToRemove(*this, Link(lk)));
|
||||
}
|
||||
if (cmds.empty()) return nullptr;
|
||||
return std::make_unique<nf7::AggregateCommand>(std::move(cmds));
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,116 +0,0 @@
|
||||
#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
|
@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/future.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class NodeRootSelectLambda : public nf7::Node::Lambda,
|
||||
public std::enable_shared_from_this<NodeRootSelectLambda> {
|
||||
public:
|
||||
using Pair = std::pair<std::string, nf7::Value>;
|
||||
|
||||
static std::shared_ptr<NodeRootSelectLambda> Create(
|
||||
const std::shared_ptr<nf7::Context>& ctx, nf7::Node& n) noexcept {
|
||||
auto ret = std::make_shared<NodeRootSelectLambda>(ctx->env(), ctx->initiator(), ctx);
|
||||
ret->target_ = n.CreateLambda(ret);
|
||||
return ret;
|
||||
}
|
||||
using nf7::Node::Lambda::Lambda;
|
||||
|
||||
void Handle(std::string_view k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
|
||||
std::unique_lock<std::mutex> lk(mtx_);
|
||||
|
||||
const auto ks = std::string {k};
|
||||
if (names_.contains(ks)) {
|
||||
names_.clear();
|
||||
auto pro = *std::exchange(pro_, std::nullopt);
|
||||
lk.unlock();
|
||||
pro.Return({ks, v});
|
||||
} else {
|
||||
q_.push_back({ks, v});
|
||||
}
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void ExecSend(std::string_view k, const nf7::Value& v) noexcept {
|
||||
env().ExecSub(shared_from_this(), [this, k = std::string {k}, v = v]() {
|
||||
target_->Handle(k, v, shared_from_this());
|
||||
});
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
nf7::Future<Pair> Select(std::unordered_set<std::string>&& names) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
assert(!pro_);
|
||||
|
||||
names_.clear();
|
||||
for (auto itr = q_.begin(); itr < q_.end(); ++itr) {
|
||||
if (names.contains(itr->first)) {
|
||||
auto p = std::move(*itr);
|
||||
q_.erase(itr);
|
||||
k.unlock();
|
||||
return {std::move(p)};
|
||||
}
|
||||
}
|
||||
pro_.emplace();
|
||||
names_ = std::move(names);
|
||||
return pro_->future();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mtx_;
|
||||
std::shared_ptr<nf7::Node::Lambda> target_;
|
||||
|
||||
std::vector<Pair> q_;
|
||||
|
||||
std::unordered_set<std::string> names_;
|
||||
std::optional<nf7::Future<Pair>::Promise> pro_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename Base, typename... I>
|
||||
struct PtrSelector final {
|
||||
public:
|
||||
PtrSelector(const std::type_info& t) noexcept : type_(&t) { }
|
||||
PtrSelector(const PtrSelector&) = delete;
|
||||
PtrSelector(PtrSelector&&) = delete;
|
||||
PtrSelector& operator=(const PtrSelector&) = delete;
|
||||
PtrSelector& operator=(PtrSelector&&) = delete;
|
||||
|
||||
template <typename T1, typename... T2>
|
||||
Base* Select(T1 ptr1, T2... ptr2) noexcept {
|
||||
auto ptr = Get<T1, I...>(ptr1);
|
||||
return ptr? ptr: Select(ptr2...);
|
||||
}
|
||||
Base* Select() noexcept { return nullptr; }
|
||||
|
||||
private:
|
||||
template <typename T, typename I1, typename... I2>
|
||||
Base* Get(T ptr) const noexcept {
|
||||
if constexpr (std::is_base_of<I1, std::remove_pointer_t<T>>::value) {
|
||||
if (*type_ == typeid(I1)) return static_cast<I1*>(ptr);
|
||||
}
|
||||
return Get<T, I2...>(ptr);
|
||||
}
|
||||
template <typename T>
|
||||
Base* Get(T) const noexcept { return nullptr; }
|
||||
|
||||
const std::type_info* const type_;
|
||||
};
|
||||
|
||||
template <typename... I>
|
||||
using InterfaceSelector = PtrSelector<File::Interface, I...>;
|
||||
|
||||
} // namespace nf7
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
// thread-safe std::deque wrapper
|
||||
template <typename T>
|
||||
class Queue {
|
||||
public:
|
||||
Queue() = default;
|
||||
Queue(const Queue&) = delete;
|
||||
Queue(Queue&&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
Queue& operator=(Queue&&) = delete;
|
||||
|
||||
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 {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (tasks_.empty()) return std::nullopt;
|
||||
auto ret = std::move(tasks_.front());
|
||||
tasks_.pop_front();
|
||||
--n_;
|
||||
k.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t size() const noexcept { return n_; }
|
||||
|
||||
protected:
|
||||
std::mutex mtx_;
|
||||
|
||||
private:
|
||||
std::atomic<size_t> n_;
|
||||
|
||||
std::deque<T> tasks_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class RingBuffer final {
|
||||
public:
|
||||
RingBuffer() = delete;
|
||||
RingBuffer(uint64_t unit, uint64_t bufn) noexcept :
|
||||
buf_(unit*bufn), unit_(unit), bufn_(bufn) {
|
||||
}
|
||||
RingBuffer(const RingBuffer&) = delete;
|
||||
RingBuffer(RingBuffer&&) = default;
|
||||
RingBuffer& operator=(const RingBuffer&) = delete;
|
||||
RingBuffer& operator=(RingBuffer&&) = default;
|
||||
|
||||
template <typename T>
|
||||
uint64_t Mix(uint64_t begin, const T* ptr, uint64_t n) noexcept {
|
||||
assert(unit_ == sizeof(T));
|
||||
|
||||
if (begin < cur_) {
|
||||
const auto drop = cur_ - begin;
|
||||
if (drop >= n) {
|
||||
return cur_;
|
||||
}
|
||||
ptr += drop;
|
||||
n -= drop;
|
||||
begin = cur_;
|
||||
}
|
||||
if (begin > cur_) {
|
||||
const auto skip = begin - cur_;
|
||||
n = std::min(bufn_ - skip, n);
|
||||
}
|
||||
auto buf = reinterpret_cast<T*>(buf_.data());
|
||||
|
||||
const auto [c, r, l] = CalcCursor(begin, n);
|
||||
for (uint64_t i = 0; i < r; ++i) {
|
||||
buf[c+i] += ptr[i];
|
||||
}
|
||||
for (uint64_t i = 0; i < l; ++i) {
|
||||
buf[i] += ptr[r+i];
|
||||
}
|
||||
return begin + n;
|
||||
}
|
||||
void Take(uint8_t* ptr, uint64_t n) noexcept {
|
||||
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||
std::memset(&buf_[c*unit_], 0, r*unit_);
|
||||
std::memset(&buf_[0*unit_], 0, l*unit_);
|
||||
cur_ += n;
|
||||
}
|
||||
|
||||
uint64_t Peek(uint64_t begin, uint8_t* ptr, uint64_t n) noexcept {
|
||||
if (cur_ > bufn_) {
|
||||
const auto actual_begin = std::max(begin, cur_-bufn_);
|
||||
const auto pad = std::min(n, actual_begin - begin);
|
||||
std::memset(ptr, 0, pad*unit_);
|
||||
begin = actual_begin;
|
||||
ptr += pad*unit_;
|
||||
n -= pad;
|
||||
}
|
||||
n = std::min(n, bufn_);
|
||||
|
||||
const auto [c, r, l] = CalcCursor(begin, n);
|
||||
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||
return begin + n;
|
||||
}
|
||||
void Write(const uint8_t* ptr, uint64_t n) noexcept {
|
||||
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||
std::memcpy(&buf_[c*unit_], &ptr[0*unit_], r*unit_);
|
||||
std::memcpy(&buf_[0*unit_], &ptr[r*unit_], l*unit_);
|
||||
cur_ += n;
|
||||
}
|
||||
|
||||
uint64_t unit() const noexcept { return unit_; }
|
||||
uint64_t bufn() const noexcept { return bufn_; }
|
||||
uint64_t cur() const noexcept { return cur_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buf_;
|
||||
uint64_t unit_;
|
||||
uint64_t bufn_;
|
||||
uint64_t cur_ = 0;
|
||||
|
||||
std::tuple<uint64_t, uint64_t, uint64_t> CalcCursor(
|
||||
uint64_t t, uint64_t n) noexcept {
|
||||
assert(n <= bufn_);
|
||||
const auto c = t % bufn_;
|
||||
const auto r = std::min(bufn_ - c, n);
|
||||
const auto l = n > r? n - r: 0;
|
||||
return {c, r, l};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,114 +0,0 @@
|
||||
#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
|
@ -1,53 +0,0 @@
|
||||
#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
|
@ -1,95 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/future.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename T>
|
||||
class Task : public nf7::Context,
|
||||
public std::enable_shared_from_this<Task<T>> {
|
||||
public:
|
||||
class Holder;
|
||||
|
||||
using Future = nf7::Future<T>;
|
||||
using Coro = typename Future::Coro;
|
||||
|
||||
using nf7::Context::Context;
|
||||
|
||||
Task(const Task&) = delete;
|
||||
Task(Task&&) = delete;
|
||||
Task& operator=(const Task&) = delete;
|
||||
Task& operator=(Task&&) = delete;
|
||||
|
||||
void Start() noexcept {
|
||||
coro_ = Proc();
|
||||
fu_ = coro_->Start(self());
|
||||
}
|
||||
void Abort() noexcept {
|
||||
coro_->Abort();
|
||||
}
|
||||
|
||||
auto self() noexcept {
|
||||
return std::enable_shared_from_this<Task<T>>::shared_from_this();
|
||||
}
|
||||
std::optional<Future>& fu() noexcept { return *fu_; }
|
||||
|
||||
protected:
|
||||
virtual Coro Proc() noexcept = 0;
|
||||
|
||||
private:
|
||||
std::optional<Coro> coro_;
|
||||
std::optional<Future> fu_;
|
||||
};
|
||||
|
||||
// all operations are not thread-safe
|
||||
template <typename T>
|
||||
class Task<T>::Holder final {
|
||||
public:
|
||||
Holder() = default;
|
||||
~Holder() noexcept {
|
||||
Abort();
|
||||
}
|
||||
|
||||
Holder(const Holder&) = delete;
|
||||
Holder(Holder&&) = delete;
|
||||
Holder& operator=(const Holder&) = delete;
|
||||
Holder& operator=(Holder&&) = delete;
|
||||
|
||||
bool CleanUp() noexcept {
|
||||
return !!std::exchange(fu_, std::nullopt);
|
||||
}
|
||||
void Abort() noexcept {
|
||||
if (auto task = task_.lock()) {
|
||||
task->Abort();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, typename... Args>
|
||||
nf7::Future<T> StartIf(Args&&... args) noexcept {
|
||||
if (fu_) return *fu_;
|
||||
|
||||
auto task = std::make_shared<U>(std::forward<Args>(args)...);
|
||||
task->Start();
|
||||
|
||||
task_ = task;
|
||||
fu_ = task->fu();
|
||||
return *fu_;
|
||||
}
|
||||
|
||||
std::optional<nf7::Future<T>>& fu() noexcept { return fu_; }
|
||||
const std::optional<nf7::Future<T>>& fu() const noexcept { return fu_; }
|
||||
|
||||
private:
|
||||
std::weak_ptr<Task<T>> task_;
|
||||
|
||||
std::optional<nf7::Future<T>> fu_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/timed_queue.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
// a thread emulation using nf7::Env::ExecAsync
|
||||
template <typename Runner, typename Task>
|
||||
class Thread final : public nf7::Context,
|
||||
public std::enable_shared_from_this<Thread<Runner, Task>> {
|
||||
public:
|
||||
Thread() = delete;
|
||||
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)) {
|
||||
}
|
||||
Thread(const Thread&) = delete;
|
||||
Thread(Thread&&) = delete;
|
||||
Thread& operator=(const Thread&) = delete;
|
||||
Thread& operator=(Thread&&) = delete;
|
||||
|
||||
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 */);
|
||||
}
|
||||
|
||||
size_t tasksDone() const noexcept { return tasks_done_; }
|
||||
|
||||
private:
|
||||
using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>;
|
||||
|
||||
Env* const env_;
|
||||
Runner runner_;
|
||||
|
||||
nf7::TimedQueue<Pair> q_;
|
||||
|
||||
std::mutex mtx_;
|
||||
bool working_ = false;
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,111 +0,0 @@
|
||||
#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(const auto& dur) noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (auto t = next_()) {
|
||||
cv_.wait_until(k, *t);
|
||||
} else {
|
||||
cv_.wait_for(k, dur);
|
||||
}
|
||||
}
|
||||
|
||||
using TimedQueue<T>::next;
|
||||
using TimedQueue<T>::size;
|
||||
|
||||
private:
|
||||
using TimedQueue<T>::mtx_;
|
||||
using TimedQueue<T>::next_;
|
||||
|
||||
std::condition_variable cv_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace nf7::util {
|
||||
|
||||
template <typename T>
|
||||
inline size_t Uniq(std::vector<T>& v) noexcept {
|
||||
size_t n = 0;
|
||||
for (auto itr = v.begin(); itr < v.end();) {
|
||||
if (v.end() != std::find(itr+1, v.end(), *itr)) {
|
||||
itr = v.erase(itr);
|
||||
++n;
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace nf7::util
|
@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace nf7::util {
|
||||
|
||||
inline std::string_view Trim(
|
||||
std::string_view str,
|
||||
const std::function<bool(char)>& func = [](auto c) { return std::isspace(c); }) noexcept {
|
||||
while (!str.empty() && func(str.front())) {
|
||||
str.remove_prefix(1);
|
||||
}
|
||||
while (!str.empty() && func(str.back())) {
|
||||
str.remove_suffix(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
|
||||
std::string_view ret;
|
||||
while (ret.empty() && i < str.size()) {
|
||||
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 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
|
301
common/value.hh
301
common/value.hh
@ -1,301 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/pair.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
#include <yas/types/std/string_view.hpp>
|
||||
#include <yas/types/std/variant.hpp>
|
||||
#include <yas/types/std/vector.hpp>
|
||||
#include <yas/types/utility/usertype.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/yas_nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Value {
|
||||
public:
|
||||
class IncompatibleException : public nf7::Exception {
|
||||
public:
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
class Data;
|
||||
using TuplePair = std::pair<std::string, nf7::Value>;
|
||||
|
||||
class Pulse { };
|
||||
using Boolean = bool;
|
||||
using Integer = int64_t;
|
||||
using Scalar = double;
|
||||
using String = std::string;
|
||||
using Vector = std::shared_ptr<std::vector<uint8_t>>;
|
||||
using Tuple = std::shared_ptr<std::vector<TuplePair>>;
|
||||
using DataPtr = std::shared_ptr<Data>;
|
||||
|
||||
using ConstVector = std::shared_ptr<const std::vector<uint8_t>>;
|
||||
using ConstTuple = std::shared_ptr<const std::vector<TuplePair>>;
|
||||
|
||||
Value() noexcept {
|
||||
}
|
||||
Value(const Value&) = default;
|
||||
Value(Value&&) = default;
|
||||
Value& operator=(const Value&) = default;
|
||||
Value& operator=(Value&&) = default;
|
||||
|
||||
Value(Pulse v) noexcept : value_(v) { }
|
||||
Value& operator=(Pulse v) noexcept { value_ = v; return *this; }
|
||||
Value(Integer v) noexcept : value_(v) { }
|
||||
Value& operator=(Integer v) noexcept { value_ = v; return *this; }
|
||||
Value(Scalar v) noexcept : value_(v) { }
|
||||
Value& operator=(Scalar v) noexcept { value_ = v; return *this; }
|
||||
Value(Boolean v) noexcept : value_(v) { }
|
||||
Value& operator=(Boolean v) noexcept { value_ = v; return *this; }
|
||||
Value(std::string_view v) noexcept : value_(std::string {v}) { }
|
||||
Value& operator=(std::string_view v) noexcept { value_ = std::string(v); return *this; }
|
||||
Value(String&& v) noexcept : value_(std::move(v)) { }
|
||||
Value& operator=(String&& v) noexcept { value_ = std::move(v); return *this; }
|
||||
Value(const Vector& v) noexcept { value_ = v; }
|
||||
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) { }
|
||||
Value& operator=(const Tuple& v) noexcept { value_ = v; return *this; }
|
||||
Value(Tuple&& v) noexcept : value_(std::move(v)) { }
|
||||
Value& operator=(Tuple&& v) noexcept { value_ = std::move(v); return *this; }
|
||||
Value(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); }
|
||||
Value& operator=(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); return *this; }
|
||||
Value(std::vector<nf7::Value>&& v) noexcept {
|
||||
std::vector<TuplePair> pairs;
|
||||
pairs.reserve(v.size());
|
||||
std::transform(v.begin(), v.end(), std::back_inserter(pairs),
|
||||
[](auto& x) { return TuplePair {"", std::move(x)}; });
|
||||
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
|
||||
}
|
||||
Value& operator=(std::vector<nf7::Value>&& v) noexcept {
|
||||
std::vector<TuplePair> pairs;
|
||||
pairs.reserve(v.size());
|
||||
std::transform(v.begin(), v.end(), std::back_inserter(pairs),
|
||||
[](auto& x) { return TuplePair {"", std::move(x)}; });
|
||||
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
|
||||
return *this;
|
||||
}
|
||||
Value(const DataPtr& v) noexcept : value_(v) { }
|
||||
Value& operator=(const DataPtr& v) noexcept { value_ = v; return *this; }
|
||||
Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
|
||||
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
|
||||
|
||||
auto Visit(auto visitor) const noexcept {
|
||||
return std::visit(visitor, value_);
|
||||
}
|
||||
|
||||
bool isPulse() const noexcept { return std::holds_alternative<Pulse>(value_); }
|
||||
bool isBoolean() const noexcept { return std::holds_alternative<Boolean>(value_); }
|
||||
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
|
||||
bool isScalar() const noexcept { return std::holds_alternative<Scalar>(value_); }
|
||||
bool isString() const noexcept { return std::holds_alternative<String>(value_); }
|
||||
bool isVector() const noexcept { return std::holds_alternative<Vector>(value_); }
|
||||
bool isTuple() const noexcept { return std::holds_alternative<Tuple>(value_); }
|
||||
bool isData() const noexcept { return std::holds_alternative<DataPtr>(value_); }
|
||||
|
||||
Integer integer() const { return get<Integer>(); }
|
||||
Boolean boolean() const { return get<Boolean>(); }
|
||||
Scalar scalar() const { return get<Scalar>(); }
|
||||
const String& string() const { return get<String>(); }
|
||||
const ConstVector vector() const { return get<Vector>(); }
|
||||
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:
|
||||
throw IncompatibleException("tuple index overflow");
|
||||
}
|
||||
const Value& tuple(std::string_view name) const {
|
||||
auto& tup = *tuple();
|
||||
auto itr = std::find_if(tup.begin(), tup.end(),
|
||||
[&name](auto& x) { return x.first == name; });
|
||||
return itr < tup.end()? itr->second:
|
||||
throw IncompatibleException("unknown tuple field: "+std::string {name});
|
||||
}
|
||||
template <typename T>
|
||||
std::shared_ptr<T> data() const {
|
||||
if (auto ptr = std::dynamic_pointer_cast<T>(data())) return ptr;
|
||||
throw IncompatibleException("data pointer downcast failure");
|
||||
}
|
||||
|
||||
Integer& integer() { return get<Integer>(); }
|
||||
Boolean& boolean() { return get<Boolean>(); }
|
||||
Scalar& scalar() { return get<Scalar>(); }
|
||||
String& string() { return get<String>(); }
|
||||
|
||||
Vector vectorUniq() { return getUniq<Vector>(); }
|
||||
Tuple tupleUniq() { return getUniq<Tuple>(); }
|
||||
|
||||
const char* typeName() const noexcept {
|
||||
struct Visitor final {
|
||||
public:
|
||||
auto operator()(Pulse) noexcept { return "pulse"; }
|
||||
auto operator()(Boolean) noexcept { return "boolean"; }
|
||||
auto operator()(Integer) noexcept { return "integer"; }
|
||||
auto operator()(Scalar) noexcept { return "scalar"; }
|
||||
auto operator()(String) noexcept { return "string"; }
|
||||
auto operator()(Vector) noexcept { return "vector"; }
|
||||
auto operator()(Tuple) noexcept { return "tuple"; }
|
||||
auto operator()(DataPtr) noexcept { return "data"; }
|
||||
};
|
||||
return Visit(Visitor{});
|
||||
}
|
||||
|
||||
template <typename Ar>
|
||||
Ar& serialize(Ar& ar) noexcept {
|
||||
ar & value_;
|
||||
return ar;
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<Pulse, Boolean, Integer, Scalar, String, Vector, Tuple, DataPtr> value_;
|
||||
|
||||
|
||||
template <typename T>
|
||||
const T& get() const
|
||||
try {
|
||||
return std::get<T>(value_);
|
||||
} catch (std::bad_variant_access&) {
|
||||
throw IncompatibleException(
|
||||
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
|
||||
}
|
||||
template <typename T>
|
||||
T& get()
|
||||
try {
|
||||
return std::get<T>(value_);
|
||||
} catch (std::bad_variant_access&) {
|
||||
throw IncompatibleException(
|
||||
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
|
||||
}
|
||||
template <typename T>
|
||||
T getUniq() {
|
||||
auto v = std::move(get<T>());
|
||||
if (v.use_count() == 1) {
|
||||
return v;
|
||||
} else {
|
||||
return std::make_shared<typename T::element_type>(*v);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Value::Data {
|
||||
public:
|
||||
Data() = default;
|
||||
virtual ~Data() = default;
|
||||
Data(const Data&) = default;
|
||||
Data(Data&&) = default;
|
||||
Data& operator=(const Data&) = default;
|
||||
Data& operator=(Data&&) = default;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::Value::Pulse> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const nf7::Value::Pulse&) {
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, nf7::Value::Pulse&) {
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::Value::Vector> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive&, const nf7::Value::Vector&) {
|
||||
throw nf7::Exception("cannot serialize Value::Vector");
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive&, nf7::Value::Vector&) {
|
||||
throw nf7::DeserializeException("cannot deserialize Value::Vector");
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::Value::Tuple> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const nf7::Value::Tuple& tup) {
|
||||
ar(*tup);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, nf7::Value::Tuple& tup) {
|
||||
ar(*tup);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::Value::DataPtr> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive&, const nf7::Value::DataPtr&) {
|
||||
throw nf7::Exception("cannot serialize Value::DataPtr");
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive&, nf7::Value::DataPtr&) {
|
||||
throw nf7::DeserializeException("cannot deserialize Value::DataPtr");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename T>
|
||||
struct EnumSerializer {
|
||||
public:
|
||||
static auto& save(auto& ar, auto t) {
|
||||
ar(magic_enum::enum_name(t));
|
||||
return ar;
|
||||
}
|
||||
static auto& load(auto& ar, auto& t) {
|
||||
std::string v;
|
||||
ar(v);
|
||||
if (auto ot = magic_enum::enum_cast<T>(v)) {
|
||||
t = *ot;
|
||||
} else {
|
||||
throw nf7::DeserializeException {"unknown enum: "+v};
|
||||
}
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
ImVec2> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const ImVec2& v) {
|
||||
ar(v.x, v.y);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, ImVec2& v) {
|
||||
ar(v.x, v.y);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ImNodes.h>
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
#include "common/yas_imgui.hh"
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
ImNodes::CanvasState> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const ImNodes::CanvasState& canvas) {
|
||||
ar(canvas.Zoom, canvas.Offset);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, ImNodes::CanvasState& canvas) {
|
||||
ar(canvas.Zoom, canvas.Offset);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
||||
|
@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::unique_ptr<nf7::File>> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::unique_ptr<nf7::File>& f) {
|
||||
ar(std::string {f->type().name()});
|
||||
|
||||
typename Archive::ChunkGuard guard {ar};
|
||||
f->Serialize(ar);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::unique_ptr<nf7::File>& f) {
|
||||
std::string name;
|
||||
ar(name);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::File::Path> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const nf7::File::Path& p) {
|
||||
p.Serialize(ar);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, nf7::File::Path& p) {
|
||||
p = {ar};
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F, typename T>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::atomic<T>> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::atomic<T>& v) {
|
||||
ar(v.load());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::atomic<T>& v) {
|
||||
T temp;
|
||||
ar(temp);
|
||||
v.store(temp);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::filesystem::path> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::filesystem::path& p) {
|
||||
ar(p.generic_string());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::filesystem::path& p) {
|
||||
std::string str;
|
||||
ar(str);
|
||||
p = std::filesystem::path(str).lexically_normal();
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,25 +0,0 @@
|
||||
#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
|
30
core/CMakeLists.txt
Normal file
30
core/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
# ---- basic modules
|
||||
set(MODS
|
||||
# ---- interface layer
|
||||
exec
|
||||
|
||||
# ---- implementation layer
|
||||
any
|
||||
lua
|
||||
null
|
||||
sdl2
|
||||
|
||||
# ---- usecase layer
|
||||
init
|
||||
test
|
||||
)
|
||||
|
||||
# ---- core library
|
||||
add_library(nf7core)
|
||||
target_link_libraries(nf7core PRIVATE nf7if)
|
||||
|
||||
foreach(name IN LISTS MODS)
|
||||
add_subdirectory(${name})
|
||||
target_link_libraries(nf7core PRIVATE nf7core_${name})
|
||||
endforeach()
|
||||
|
||||
# ---- generate all.h
|
||||
target_meta_source(nf7core
|
||||
PRIVATE all.c.sh
|
||||
ARGS ${MODS}
|
||||
)
|
36
core/all.c.sh
Executable file
36
core/all.c.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "#include \"core/all.h\""
|
||||
echo
|
||||
echo "#include <assert.h>"
|
||||
echo
|
||||
echo "#include \"util/log.h\""
|
||||
echo
|
||||
echo "const uint32_t NF7CORE_MAX_MODS = UINT32_C($#);"
|
||||
echo
|
||||
echo "uint32_t nf7core_new(struct nf7* nf7, struct nf7_mod** mods) {"
|
||||
echo " assert(nullptr != nf7);"
|
||||
echo " assert(nullptr != mods);"
|
||||
echo
|
||||
echo " nf7->mods.ptr = mods;"
|
||||
echo " nf7->mods.n = 0;"
|
||||
echo
|
||||
for name in $@; do
|
||||
echo " {"
|
||||
echo " extern const struct nf7_mod_meta nf7core_${name};"
|
||||
echo " extern struct nf7_mod* nf7core_${name}_new(const struct nf7*);"
|
||||
echo " nf7util_log_debug(\"loading module: %s\", nf7core_${name}.name);"
|
||||
echo " struct nf7_mod* mod = nf7core_${name}_new(nf7);"
|
||||
echo " if (nullptr != mod) {"
|
||||
echo " assert(nullptr != mod->nf7);"
|
||||
echo " assert(nullptr != mod->meta);"
|
||||
echo " nf7->mods.ptr[nf7->mods.n++] = mod;"
|
||||
echo " nf7util_log_info(\"loaded module: %s\", nf7core_${name}.name);"
|
||||
echo " } else {"
|
||||
echo " nf7util_log_warn(\"failed to load module: %s\", nf7core_${name}.name);"
|
||||
echo " }"
|
||||
echo " }"
|
||||
echo
|
||||
done
|
||||
echo " return nf7->mods.n;"
|
||||
echo "}"
|
14
core/all.h
Normal file
14
core/all.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
|
||||
// A maximum number of modules which nf7core may provide.
|
||||
extern const uint32_t NF7CORE_MAX_MODS;
|
||||
|
||||
|
||||
// Initializes all modules and returns their instances.
|
||||
// sizeof(mods)/sizeof(mods[0]) should be equal to NF7CORE_MAX_MODS
|
||||
uint32_t nf7core_new(struct nf7* nf7, struct nf7_mod** mods);
|
15
core/any/CMakeLists.txt
Normal file
15
core/any/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
add_library(nf7core_any)
|
||||
target_sources(nf7core_any
|
||||
PRIVATE
|
||||
idea.c
|
||||
mod.c
|
||||
)
|
||||
target_link_libraries(nf7core_any
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
|
||||
PUBLIC
|
||||
nf7core_exec
|
||||
)
|
||||
|
103
core/any/idea.c
Normal file
103
core/any/idea.c
Normal file
@ -0,0 +1,103 @@
|
||||
// No copyright
|
||||
#include "core/any/idea.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/malloc.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
|
||||
|
||||
struct nf7core_any_entity {
|
||||
struct nf7core_exec_entity super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
|
||||
struct nf7core_exec_entity* entity;
|
||||
};
|
||||
|
||||
struct nf7core_exec_entity* new_(struct nf7core_exec*);
|
||||
void del_(struct nf7core_exec_entity*);
|
||||
void send_(struct nf7core_exec_entity*, struct nf7util_buffer*);
|
||||
|
||||
|
||||
struct nf7core_exec_entity* new_(struct nf7core_exec* exec) {
|
||||
assert(nullptr != exec);
|
||||
|
||||
struct nf7core_any_entity* this = nf7util_malloc_alloc(exec->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate new entity");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_any_entity) {
|
||||
.super = {
|
||||
.idea = &nf7core_any_idea,
|
||||
.mod = exec,
|
||||
},
|
||||
.malloc = exec->malloc,
|
||||
};
|
||||
return &this->super;
|
||||
}
|
||||
|
||||
void del_(struct nf7core_exec_entity* entity) {
|
||||
struct nf7core_any_entity* this = (void*) entity;
|
||||
if (nullptr != this) {
|
||||
nf7core_exec_entity_del(this->entity);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
void send_(struct nf7core_exec_entity* entity, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != entity);
|
||||
assert(nullptr != buf);
|
||||
|
||||
struct nf7core_any_entity* this = (void*) entity;
|
||||
if (nullptr != this->entity) {
|
||||
nf7core_exec_entity_send(this->entity, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// get name of the requested idea
|
||||
const uint8_t* name = buf->array.ptr;
|
||||
const uint64_t namelen = buf->array.n;
|
||||
if (0U == namelen) {
|
||||
nf7util_log_warn("expected an idea name, but got an empty string");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
// find the requested idea and create new entity
|
||||
this->entity = nf7core_exec_entity_new(this->super.mod, name, namelen);
|
||||
|
||||
// assign the return value
|
||||
struct nf7util_buffer* result = nullptr;
|
||||
if (nullptr != this->entity) {
|
||||
result = nf7util_buffer_new_from_cstr(this->malloc, "");
|
||||
nf7util_log_debug("sub-entity is created: %.*s", (int) namelen, name);
|
||||
} else {
|
||||
result = nf7util_buffer_new_from_cstr(this->malloc, "FAIL");
|
||||
nf7util_log_warn("unknown idea requested: %.*s", (int) namelen, name);
|
||||
}
|
||||
|
||||
// return the result
|
||||
if (nullptr == result) {
|
||||
nf7util_log_error("failed to allocate a buffer to return result");
|
||||
goto EXIT;
|
||||
}
|
||||
nf7core_exec_entity_recv(&this->super, result);
|
||||
|
||||
EXIT:
|
||||
nf7util_buffer_unref(buf);
|
||||
}
|
||||
|
||||
|
||||
const struct nf7core_exec_idea nf7core_any_idea = {
|
||||
.name = (const uint8_t*) "nf7core_any",
|
||||
.details = (const uint8_t*) "creates and wraps other entity of an idea chosen at runtime",
|
||||
.mod = &nf7core_any,
|
||||
|
||||
.new = new_,
|
||||
.del = del_,
|
||||
.send = send_,
|
||||
};
|
7
core/any/idea.h
Normal file
7
core/any/idea.h
Normal file
@ -0,0 +1,7 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "core/any/mod.h"
|
||||
|
||||
|
||||
extern const struct nf7core_exec_idea nf7core_any_idea;
|
66
core/any/mod.c
Normal file
66
core/any/mod.c
Normal file
@ -0,0 +1,66 @@
|
||||
// No copyright
|
||||
#include "core/any/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/any/idea.h"
|
||||
#include "core/exec/idea.h"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
|
||||
|
||||
struct nf7_mod* nf7core_any_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
// find nf7core_exec
|
||||
struct nf7core_exec* exec = (void*) nf7_get_mod_by_meta(nf7, &nf7core_exec);
|
||||
if (nullptr == exec) {
|
||||
nf7util_log_error("not found nf7core_exec, nf7core_any is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// create module data
|
||||
struct nf7core_any* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_any) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_any,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.exec = exec,
|
||||
};
|
||||
|
||||
// register idea
|
||||
if (!nf7core_exec_idea_register(exec, &nf7core_any_idea)) {
|
||||
nf7util_log_error("failed to register an idea, nf7core_any");
|
||||
goto ABORT;
|
||||
}
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
del_(&this->super);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_any* this = (void*) mod;
|
||||
if (nullptr != this) {
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_any = {
|
||||
.name = (const uint8_t*) "nf7core_any",
|
||||
.desc = (const uint8_t*) "executes any things",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
28
core/any/mod.h
Normal file
28
core/any/mod.h
Normal file
@ -0,0 +1,28 @@
|
||||
// No copyright
|
||||
//
|
||||
// This module provides an idea, nf7core_any. Its entity can create and wrap
|
||||
// other entity of any idea chosen at runtime.
|
||||
//
|
||||
// An entity of idea, nf7core_any, is composed of 2 states:
|
||||
// INIT state:
|
||||
// The entity is in this state right after the born. Accepts a string that
|
||||
// expressing idea name and returns a string. If the returned string is
|
||||
// empty, transitions to PIPE state with an sub-entity of the specified idea,
|
||||
// otherwise error.
|
||||
// PIPE state:
|
||||
// All buffers from the client is passed to the sub-entity, and from the
|
||||
// sub-entity is to the cleint.
|
||||
#pragma once
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_any {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
struct nf7core_exec* exec;
|
||||
};
|
||||
extern const struct nf7_mod_meta nf7core_any;
|
14
core/exec/CMakeLists.txt
Normal file
14
core/exec/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
add_library(nf7core_exec)
|
||||
target_sources(nf7core_exec
|
||||
PRIVATE
|
||||
mod.c
|
||||
PUBLIC
|
||||
entity.h
|
||||
idea.h
|
||||
mod.h
|
||||
)
|
||||
target_link_libraries(nf7core_exec
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
)
|
74
core/exec/entity.h
Normal file
74
core/exec/entity.h
Normal file
@ -0,0 +1,74 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/exec/idea.h"
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_exec_entity {
|
||||
const struct nf7core_exec_idea* idea;
|
||||
struct nf7core_exec* mod;
|
||||
|
||||
void* data;
|
||||
void (*on_recv)(
|
||||
struct nf7core_exec_entity*,
|
||||
struct nf7util_buffer*);
|
||||
};
|
||||
|
||||
|
||||
static inline struct nf7core_exec_entity* nf7core_exec_entity_new(
|
||||
struct nf7core_exec* mod, const uint8_t* name, size_t namelen) {
|
||||
const struct nf7core_exec_idea* idea =
|
||||
nf7core_exec_idea_find(mod, name, namelen);
|
||||
if (nullptr == idea) {
|
||||
nf7util_log_error("missing idea: %.*s", (int) namelen, name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct nf7core_exec_entity* entity = idea->new(mod);
|
||||
if (nullptr == entity) {
|
||||
nf7util_log_error("failed to create entity of '%.*s'", (int) namelen, name);
|
||||
return nullptr;
|
||||
}
|
||||
assert(idea == entity->idea);
|
||||
assert(mod == entity->mod);
|
||||
return entity;
|
||||
}
|
||||
|
||||
// The implementation of entity use this to send buffer to the client.
|
||||
// Takes ownership of buf.
|
||||
static inline void nf7core_exec_entity_recv(struct nf7core_exec_entity* this, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != buf);
|
||||
|
||||
if (nullptr != this->on_recv) {
|
||||
this->on_recv(this, buf);
|
||||
} else {
|
||||
nf7util_buffer_unref(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// The client of entity use this to send buffer to the implementation.
|
||||
// Takes ownership of buf.
|
||||
static inline void nf7core_exec_entity_send(
|
||||
struct nf7core_exec_entity* this, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != this->idea);
|
||||
assert(nullptr != this->idea->send);
|
||||
assert(nullptr != buf);
|
||||
|
||||
this->idea->send(this, buf);
|
||||
}
|
||||
|
||||
static inline void nf7core_exec_entity_del(struct nf7core_exec_entity* this) {
|
||||
if (nullptr != this) {
|
||||
assert(nullptr != this->idea);
|
||||
assert(nullptr != this->idea->del);
|
||||
|
||||
this->idea->del(this);
|
||||
}
|
||||
}
|
48
core/exec/idea.h
Normal file
48
core/exec/idea.h
Normal file
@ -0,0 +1,48 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/buffer.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_exec_idea {
|
||||
const uint8_t* name;
|
||||
const uint8_t* details;
|
||||
const struct nf7_mod_meta* mod;
|
||||
|
||||
struct nf7core_exec_entity* (*new)(struct nf7core_exec*);
|
||||
void (*del)(struct nf7core_exec_entity*);
|
||||
|
||||
void (*send)(
|
||||
struct nf7core_exec_entity*,
|
||||
struct nf7util_buffer*);
|
||||
};
|
||||
|
||||
|
||||
static inline bool nf7core_exec_idea_register(
|
||||
struct nf7core_exec* mod, const struct nf7core_exec_idea* idea) {
|
||||
assert(nullptr != mod);
|
||||
assert(nullptr != idea);
|
||||
|
||||
return nf7core_exec_ideas_insert(&mod->ideas, UINT64_MAX, idea);
|
||||
}
|
||||
|
||||
static inline const struct nf7core_exec_idea* nf7core_exec_idea_find(
|
||||
const struct nf7core_exec* mod, const uint8_t* name, size_t namelen) {
|
||||
assert(nullptr != mod);
|
||||
assert(nullptr != name || 0U == namelen);
|
||||
|
||||
for (uint32_t i = 0; i < mod->ideas.n; ++i) {
|
||||
const struct nf7core_exec_idea* idea = mod->ideas.ptr[i];
|
||||
if (nf7util_str_equal_cstr(name, namelen, idea->name)) {
|
||||
return idea;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
50
core/exec/mod.c
Normal file
50
core/exec/mod.c
Normal file
@ -0,0 +1,50 @@
|
||||
// No copyright
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
|
||||
|
||||
struct nf7_mod* nf7core_exec_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_exec* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
goto ABORT;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_exec) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_exec,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
};
|
||||
|
||||
nf7core_exec_ideas_init(&this->ideas, this->malloc);
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting module init");
|
||||
del_((struct nf7_mod*) this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_exec* this = (void*) mod;
|
||||
nf7core_exec_ideas_deinit(&this->ideas);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_exec = {
|
||||
.name = (const uint8_t*) "nf7core_exec",
|
||||
.desc = (const uint8_t*) "provides a registry for executables",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
25
core/exec/mod.h
Normal file
25
core/exec/mod.h
Normal file
@ -0,0 +1,25 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/array.h"
|
||||
#include "util/malloc.h"
|
||||
|
||||
|
||||
struct nf7core_exec_idea;
|
||||
struct nf7core_exec_entity;
|
||||
|
||||
NF7UTIL_ARRAY_INLINE(nf7core_exec_ideas, const struct nf7core_exec_idea*);
|
||||
|
||||
|
||||
struct nf7core_exec {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7* nf7;
|
||||
struct nf7util_malloc* malloc;
|
||||
|
||||
struct nf7core_exec_ideas ideas;
|
||||
};
|
||||
|
||||
extern const struct nf7_mod_meta nf7core_exec;
|
12
core/init/CMakeLists.txt
Normal file
12
core/init/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_library(nf7core_init)
|
||||
target_sources(nf7core_init
|
||||
PRIVATE
|
||||
mod.c
|
||||
factory.priv.h
|
||||
)
|
||||
target_link_libraries(nf7core_init
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
)
|
||||
|
95
core/init/factory.priv.h
Normal file
95
core/init/factory.priv.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_init_factory {
|
||||
struct nf7* nf7;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
uv_idle_t idle;
|
||||
|
||||
const uint8_t* idea_name;
|
||||
size_t idea_namelen;
|
||||
|
||||
void* data;
|
||||
void (*on_created)(struct nf7core_init_factory*, struct nf7core_exec_entity*);
|
||||
// after the callback is called
|
||||
};
|
||||
|
||||
static struct nf7core_init_factory* factory_new_(struct nf7*, const uint8_t*, size_t);
|
||||
static void factory_del_(struct nf7core_init_factory*);
|
||||
static void factory_on_idle_(uv_idle_t*);
|
||||
static void factory_on_close_(uv_handle_t*);
|
||||
|
||||
|
||||
static inline struct nf7core_init_factory* factory_new_(
|
||||
struct nf7* nf7, const uint8_t* idea_name, size_t idea_namelen) {
|
||||
struct nf7core_init_factory* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate the first factory");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_init_factory) {
|
||||
.nf7 = nf7,
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
.idea_name = idea_name,
|
||||
.idea_namelen = idea_namelen,
|
||||
};
|
||||
|
||||
uv_idle_init(this->uv, &this->idle);
|
||||
this->idle.data = this;
|
||||
|
||||
uv_idle_start(&this->idle, factory_on_idle_);
|
||||
return this;
|
||||
}
|
||||
|
||||
static inline void factory_del_(struct nf7core_init_factory* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
if (nullptr != this->idle.data) {
|
||||
uv_idle_stop(&this->idle);
|
||||
uv_close((uv_handle_t*) &this->idle, factory_on_close_);
|
||||
return;
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static inline void factory_on_idle_(uv_idle_t* idle) {
|
||||
assert(nullptr != idle);
|
||||
struct nf7core_init_factory* this = idle->data;
|
||||
|
||||
struct nf7core_exec* exec = (void*) nf7_get_mod_by_meta(this->nf7, &nf7core_exec);
|
||||
if (nullptr == exec) {
|
||||
nf7util_log_error("nf7core_exec module is not installed");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
struct nf7core_exec_entity* entity = nf7core_exec_entity_new(exec, this->idea_name, this->idea_namelen);
|
||||
if (nullptr == entity) {
|
||||
nf7util_log_error("failed to create new entity");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
assert(nullptr != this->on_created);
|
||||
this->on_created(this, entity);
|
||||
|
||||
EXIT:
|
||||
uv_idle_stop(&this->idle);
|
||||
}
|
||||
static inline void factory_on_close_(uv_handle_t* handle) {
|
||||
assert(nullptr != handle);
|
||||
struct nf7core_init_factory* this = handle->data;
|
||||
handle->data = nullptr;
|
||||
factory_del_(this);
|
||||
}
|
97
core/init/mod.c
Normal file
97
core/init/mod.c
Normal file
@ -0,0 +1,97 @@
|
||||
// No copyright
|
||||
#include "core/init/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
#include "core/init/factory.priv.h"
|
||||
|
||||
|
||||
#define IDEA_NAME "nf7core_any"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
static void start_(struct nf7core_init_factory*, struct nf7core_exec_entity*);
|
||||
|
||||
struct nf7_mod* nf7core_init_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_init* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
goto ABORT;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_init) {
|
||||
.super = {
|
||||
.meta = &nf7core_init,
|
||||
.nf7 = nf7,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
};
|
||||
|
||||
this->factory = factory_new_(nf7, (const uint8_t*) IDEA_NAME, sizeof(IDEA_NAME)-1);
|
||||
if (nullptr == this->factory) {
|
||||
nf7util_log_error("failed to start the first factory");
|
||||
goto ABORT;
|
||||
}
|
||||
this->factory->data = this;
|
||||
this->factory->on_created = start_;
|
||||
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
del_(&this->super);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_init* this = (void*) mod;
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
nf7util_log_info("delete factory");
|
||||
factory_del_(this->factory);
|
||||
this->factory = nullptr;
|
||||
nf7core_exec_entity_del(this->entity);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void start_(struct nf7core_init_factory* factory, struct nf7core_exec_entity* entity) {
|
||||
assert(nullptr != factory);
|
||||
struct nf7core_init* this = factory->data;
|
||||
assert(nullptr != this);
|
||||
|
||||
this->entity = entity;
|
||||
if (nullptr == this->entity) {
|
||||
nf7util_log_warn("failed to create new entity");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
struct nf7util_buffer* buf = nf7util_buffer_new(this->malloc, 0);
|
||||
if (nullptr == buf) {
|
||||
nf7util_log_error("failed to allocate an empty buffer to send as the first trigger");
|
||||
goto EXIT;
|
||||
}
|
||||
nf7core_exec_entity_send(this->entity, buf);
|
||||
|
||||
EXIT:
|
||||
this->factory = nullptr;
|
||||
factory_del_(factory);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_init = {
|
||||
.name = (const uint8_t*) "nf7core_init",
|
||||
.desc = (const uint8_t*) "creates the first entity",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
22
core/init/mod.h
Normal file
22
core/init/mod.h
Normal file
@ -0,0 +1,22 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
|
||||
|
||||
struct nf7core_init_factory;
|
||||
|
||||
struct nf7core_init {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
|
||||
struct nf7core_init_factory* factory;
|
||||
struct nf7core_exec_entity* entity;
|
||||
};
|
||||
extern const struct nf7_mod_meta nf7core_init;
|
21
core/lua/CMakeLists.txt
Normal file
21
core/lua/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
add_library(nf7core_lua)
|
||||
target_sources(nf7core_lua
|
||||
PRIVATE
|
||||
mod.c
|
||||
thread.c
|
||||
value_ptr.c
|
||||
PUBLIC
|
||||
mod.h
|
||||
thread.h
|
||||
value.h
|
||||
value_ptr.h
|
||||
)
|
||||
target_link_libraries(nf7core_lua
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
luajit
|
||||
)
|
||||
target_tests(nf7core_lua
|
||||
thread.test.c
|
||||
)
|
66
core/lua/mod.c
Normal file
66
core/lua/mod.c
Normal file
@ -0,0 +1,66 @@
|
||||
// No copyright
|
||||
#include "core/lua/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
|
||||
static void del_(struct nf7core_lua*);
|
||||
|
||||
struct nf7_mod* nf7core_lua_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_lua* this =
|
||||
nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate a module context");
|
||||
goto ABORT;
|
||||
}
|
||||
*this = (struct nf7core_lua) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_lua,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
};
|
||||
|
||||
this->thread = nf7core_lua_thread_new(this, nullptr, nullptr);
|
||||
if (nullptr == this->thread) {
|
||||
nf7util_log_error("failed to create main thread");
|
||||
goto ABORT;
|
||||
}
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting lua module init");
|
||||
del_(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_lua* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullptr != this->thread) {
|
||||
nf7core_lua_thread_unref(this->thread);
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void del_mod_(struct nf7_mod* mod) {
|
||||
struct nf7core_lua* this = (void*) mod;
|
||||
del_(this);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_lua = {
|
||||
.name = (const uint8_t*) "nf7core_lua",
|
||||
.desc = (const uint8_t*) "lua script execution",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_mod_,
|
||||
};
|
25
core/lua/mod.h
Normal file
25
core/lua/mod.h
Normal file
@ -0,0 +1,25 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lua.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/malloc.h"
|
||||
#include "util/refcnt.h"
|
||||
|
||||
|
||||
extern const struct nf7_mod_meta nf7core_lua;
|
||||
|
||||
struct nf7core_lua {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
|
||||
struct nf7core_lua_thread* thread;
|
||||
};
|
||||
|
||||
struct nf7_mod* nf7core_lua_new(struct nf7*);
|
180
core/lua/thread.c
Normal file
180
core/lua/thread.c
Normal file
@ -0,0 +1,180 @@
|
||||
// No copyright
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
static void* alloc_(void*, void*, size_t, size_t);
|
||||
static void del_(struct nf7core_lua_thread*);
|
||||
|
||||
static void on_time_(uv_timer_t*);
|
||||
static void on_close_(uv_handle_t*);
|
||||
|
||||
NF7UTIL_REFCNT_IMPL(, nf7core_lua_thread, {del_(this);});
|
||||
|
||||
struct nf7core_lua_thread* nf7core_lua_thread_new(
|
||||
struct nf7core_lua* mod,
|
||||
struct nf7core_lua_thread* base,
|
||||
struct nf7core_lua_value_ptr* func) {
|
||||
assert(nullptr != mod);
|
||||
|
||||
struct nf7core_lua_thread* this =
|
||||
nf7util_malloc_alloc(mod->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate new thread context");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_lua_thread) {
|
||||
.mod = mod,
|
||||
.malloc = mod->malloc,
|
||||
.uv = mod->uv,
|
||||
};
|
||||
|
||||
if (0 != nf7util_log_uv(uv_timer_init(this->uv, &this->timer))) {
|
||||
nf7util_log_error("failed to init uv timer");
|
||||
goto ABORT;
|
||||
}
|
||||
this->timer.data = this;
|
||||
|
||||
if (nullptr != base) {
|
||||
this->lua = lua_newthread(base->lua);
|
||||
if (nullptr == this->lua) {
|
||||
nf7util_log_error("failed to allocate new lua thread");
|
||||
goto ABORT;
|
||||
}
|
||||
this->base = base;
|
||||
nf7core_lua_thread_ref(this->base);
|
||||
} else {
|
||||
this->lua_owned = true;
|
||||
this->lua = lua_newstate(alloc_, this);
|
||||
if (nullptr == this->lua) {
|
||||
nf7util_log_error("failed to allocate new lua state");
|
||||
goto ABORT;
|
||||
}
|
||||
nf7util_log_debug("new lua state is created");
|
||||
}
|
||||
|
||||
if (nullptr != func) {
|
||||
this->state = NF7CORE_LUA_THREAD_PAUSED;
|
||||
nf7core_lua_value_ptr_push(func, this->lua);
|
||||
} else {
|
||||
this->state = NF7CORE_LUA_THREAD_DONE;
|
||||
}
|
||||
|
||||
nf7core_lua_thread_ref(this);
|
||||
return this;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting thread creation");
|
||||
del_(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nf7core_lua_thread_resume_varg_after(
|
||||
struct nf7core_lua_thread* this, uint64_t timeout, va_list vargs) {
|
||||
assert(nullptr != this);
|
||||
assert(NF7CORE_LUA_THREAD_PAUSED == this->state);
|
||||
|
||||
// stores parameters
|
||||
for (uint32_t i = 0;; ++i) {
|
||||
struct nf7core_lua_value* src = va_arg(vargs, struct nf7core_lua_value*);
|
||||
if (nullptr == src) {
|
||||
this->args.n = i;
|
||||
break;
|
||||
}
|
||||
assert(i < NF7CORE_LUA_THREAD_MAX_ARGS &&
|
||||
"too many args or forgotten last nullptr");
|
||||
|
||||
struct nf7core_lua_value* dst = &this->args.ptr[i];
|
||||
if (!nf7core_lua_value_set(dst, src)) {
|
||||
nf7core_lua_value_set(dst, &NF7CORE_LUA_VALUE_NIL());
|
||||
nf7util_log_warn(
|
||||
"failed to store parameter value, it's replaced by nil");
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != nf7util_log_uv(uv_timer_start(&this->timer, on_time_, timeout, 0))) {
|
||||
nf7util_log_error(
|
||||
"failed to start timer for resuming thread");
|
||||
return false;
|
||||
}
|
||||
nf7core_lua_thread_ref(this);
|
||||
|
||||
nf7util_log_debug("lua thread state change: PAUSED -> SCHEDULED");
|
||||
this->state = NF7CORE_LUA_THREAD_SCHEDULED;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* alloc_(void* data, void* ptr, size_t, size_t nsize) {
|
||||
struct nf7core_lua_thread* this = data;
|
||||
return nf7util_malloc_realloc(this->malloc, ptr, nsize);
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_lua_thread* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
if (nullptr != this->timer.data) {
|
||||
uv_close((uv_handle_t*) &this->timer, on_close_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullptr != this->lua && this->lua_owned) {
|
||||
lua_close(this->lua);
|
||||
nf7util_log_debug("lua state is closed");
|
||||
}
|
||||
if (nullptr != this->base) {
|
||||
nf7core_lua_thread_unref(this->base);
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void on_time_(uv_timer_t* timer) {
|
||||
struct nf7core_lua_thread* this = timer->data;
|
||||
assert(nullptr != this);
|
||||
assert(NF7CORE_LUA_THREAD_SCHEDULED == this->state);
|
||||
|
||||
lua_State* L = this->lua;
|
||||
for (uint32_t i = 0; i < this->args.n; ++i) {
|
||||
struct nf7core_lua_value* v = &this->args.ptr[i];
|
||||
nf7core_lua_value_push(v, L);
|
||||
nf7core_lua_value_unset(v);
|
||||
}
|
||||
|
||||
nf7util_log_debug("lua thread state change: SCHEDULED -> RUNNING");
|
||||
this->state = NF7CORE_LUA_THREAD_RUNNING;
|
||||
|
||||
const int result = lua_resume(L, (int) this->args.n);
|
||||
switch (result) {
|
||||
case 0:
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> DONE");
|
||||
this->state = NF7CORE_LUA_THREAD_DONE;
|
||||
break;
|
||||
case LUA_YIELD:
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> PAUSED");
|
||||
this->state = NF7CORE_LUA_THREAD_PAUSED;
|
||||
break;
|
||||
case LUA_ERRMEM:
|
||||
case LUA_ERRRUN:
|
||||
case LUA_ERRERR:
|
||||
nf7util_log_warn("lua execution failed: ", lua_tostring(L, -1));
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> ABORTED");
|
||||
this->state = NF7CORE_LUA_THREAD_ABORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (nullptr != this->post_exec) {
|
||||
this->post_exec(this, L);
|
||||
}
|
||||
lua_settop(L, 0);
|
||||
|
||||
nf7core_lua_thread_unref(this);
|
||||
}
|
||||
|
||||
static void on_close_(uv_handle_t* handle) {
|
||||
struct nf7core_lua_thread* this = handle->data;
|
||||
this->timer.data = nullptr;
|
||||
del_(this);
|
||||
}
|
92
core/lua/thread.h
Normal file
92
core/lua/thread.h
Normal file
@ -0,0 +1,92 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <lua.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include "util/malloc.h"
|
||||
#include "util/refcnt.h"
|
||||
|
||||
#include "core/lua/mod.h"
|
||||
#include "core/lua/value.h"
|
||||
#include "core/lua/value_ptr.h"
|
||||
|
||||
|
||||
struct nf7core_lua_thread {
|
||||
struct nf7core_lua* mod;
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
|
||||
bool lua_owned;
|
||||
lua_State* lua;
|
||||
|
||||
struct nf7core_lua_thread* base;
|
||||
|
||||
uv_timer_t timer;
|
||||
|
||||
uint32_t refcnt;
|
||||
|
||||
uint8_t state;
|
||||
# define NF7CORE_LUA_THREAD_PAUSED 0
|
||||
# define NF7CORE_LUA_THREAD_SCHEDULED 1
|
||||
# define NF7CORE_LUA_THREAD_RUNNING 2
|
||||
# define NF7CORE_LUA_THREAD_DONE 3
|
||||
# define NF7CORE_LUA_THREAD_ABORTED 4
|
||||
|
||||
struct {
|
||||
# define NF7CORE_LUA_THREAD_MAX_ARGS 4
|
||||
uint32_t n;
|
||||
struct nf7core_lua_value ptr[NF7CORE_LUA_THREAD_MAX_ARGS];
|
||||
} args;
|
||||
|
||||
void* data;
|
||||
void (*post_exec)(struct nf7core_lua_thread*, lua_State*);
|
||||
};
|
||||
NF7UTIL_REFCNT_DECL(, nf7core_lua_thread);
|
||||
|
||||
|
||||
// Creates and returns new thread.
|
||||
struct nf7core_lua_thread* nf7core_lua_thread_new(
|
||||
struct nf7core_lua* mod,
|
||||
struct nf7core_lua_thread* base,
|
||||
struct nf7core_lua_value_ptr* func);
|
||||
// POSTCONDS:
|
||||
// - If the base is not nullptr, the returned thread must be synchronized with
|
||||
// the base, otherwise, it's completely independent. (base thread)
|
||||
// - If the func is not nullptr, the returned thread prepares to execute a
|
||||
// function in the value,
|
||||
// otherwise, it can execute nothing (this is for the base thread)
|
||||
|
||||
// Resumes the co-routine with the values.
|
||||
bool nf7core_lua_thread_resume_varg_after(
|
||||
struct nf7core_lua_thread* this, uint64_t timeout, va_list vargs);
|
||||
// PRECONDS:
|
||||
// - `nullptr != this`
|
||||
// - `nullptr != this->func`
|
||||
// - `NF7CORE_LUA_THREAD_PAUSED == this->state`
|
||||
// - Items in `vargs` must be `const struct nf7core_lua_value*` or `nullptr`.
|
||||
// - The last item of `vargs` must be `nullptr`.
|
||||
// POSTCONDS:
|
||||
// - When returns true:
|
||||
// - `NF7CORE_LUA_THREAD_SCHEDULED == this->state`
|
||||
// - the state will changes to RUNNING after `timeout` [ms]
|
||||
// - Otherwise, nothing happens.
|
||||
|
||||
static inline bool nf7core_lua_thread_resume(
|
||||
struct nf7core_lua_thread* this, ...) {
|
||||
va_list vargs;
|
||||
va_start(vargs, this);
|
||||
const bool ret = nf7core_lua_thread_resume_varg_after(this, 0, vargs);
|
||||
va_end(vargs);
|
||||
return ret;
|
||||
}
|
||||
static inline bool nf7core_lua_thread_resume_after(
|
||||
struct nf7core_lua_thread* this, uint64_t timeout, ...) {
|
||||
va_list vargs;
|
||||
va_start(vargs, this);
|
||||
const bool ret = nf7core_lua_thread_resume_varg_after(this, timeout, vargs);
|
||||
va_end(vargs);
|
||||
return ret;
|
||||
}
|
67
core/lua/thread.test.c
Normal file
67
core/lua/thread.test.c
Normal file
@ -0,0 +1,67 @@
|
||||
// No copyright
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "test/common.h"
|
||||
|
||||
|
||||
static void finalize_(struct nf7core_lua_thread* this, lua_State*) {
|
||||
struct nf7test* test_ = this->data;
|
||||
nf7test_expect(NF7CORE_LUA_THREAD_DONE == this->state);
|
||||
nf7test_unref(test_);
|
||||
}
|
||||
|
||||
NF7TEST(nf7core_lua_thread_test_valid_syntax) {
|
||||
struct nf7core_lua* mod =
|
||||
(void*) nf7_get_mod_by_meta(test_->nf7, &nf7core_lua);
|
||||
if (!nf7test_expect(nullptr != mod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct nf7core_lua_thread* base = mod->thread;
|
||||
lua_State* L = base->lua;
|
||||
|
||||
if (!nf7test_expect(0 == luaL_loadstring(L, "local x = 100"))) {
|
||||
nf7util_log_error("lua compile error: %s", lua_tostring(L, -1));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct nf7core_lua_value_ptr* func = nf7core_lua_value_ptr_new(base, L);
|
||||
if (!nf7test_expect(nullptr != func)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct nf7core_lua_thread* thread = nf7core_lua_thread_new(mod, base, func);
|
||||
nf7core_lua_value_ptr_unref(func);
|
||||
if (!nf7test_expect(nullptr != thread)) {
|
||||
return false;
|
||||
}
|
||||
thread->data = test_;
|
||||
thread->post_exec = finalize_;
|
||||
|
||||
if (!nf7test_expect(nf7core_lua_thread_resume(thread, nullptr))) {
|
||||
return false;
|
||||
}
|
||||
nf7core_lua_thread_unref(thread);
|
||||
nf7test_ref(test_);
|
||||
return true;
|
||||
}
|
||||
|
||||
NF7TEST(nf7core_lua_thread_test_invalid_syntax) {
|
||||
struct nf7core_lua* mod =
|
||||
(void*) nf7_get_mod_by_meta(test_->nf7, &nf7core_lua);
|
||||
if (!nf7test_expect(nullptr != mod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct nf7core_lua_thread* base = mod->thread;
|
||||
lua_State* L = base->lua;
|
||||
|
||||
if (!nf7test_expect(0 != luaL_loadstring(L, "helloworld"))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
88
core/lua/value.h
Normal file
88
core/lua/value.h
Normal file
@ -0,0 +1,88 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "core/lua/value_ptr.h"
|
||||
|
||||
|
||||
struct nf7core_lua_value {
|
||||
uint32_t type;
|
||||
# define NF7CORE_LUA_VALUE_TYPE_NIL 0
|
||||
# define NF7CORE_LUA_VALUE_TYPE_INT 1
|
||||
# define NF7CORE_LUA_VALUE_TYPE_NUM 2
|
||||
# define NF7CORE_LUA_VALUE_TYPE_PTR 3
|
||||
union {
|
||||
lua_Integer i;
|
||||
lua_Number n;
|
||||
struct nf7core_lua_value_ptr* ptr;
|
||||
};
|
||||
};
|
||||
|
||||
#define NF7CORE_LUA_VALUE_NIL() \
|
||||
(struct nf7core_lua_value) { .type = NF7CORE_LUA_VALUE_TYPE_NIL, }
|
||||
#define NF7CORE_LUA_VALUE_INT(v) \
|
||||
(struct nf7core_lua_value) { .type = NF7CORE_LUA_VALUE_TYPE_INT, .i = (v), }
|
||||
#define NF7CORE_LUA_VALUE_NUM(v) \
|
||||
(struct nf7core_lua_value) { .type = NF7CORE_LUA_VALUE_TYPE_NUM, .n = (v), }
|
||||
#define NF7CORE_LUA_VALUE_PTR(v) \
|
||||
(struct nf7core_lua_value) { \
|
||||
.type = NF7CORE_LUA_VALUE_TYPE_PTR, \
|
||||
.ptr = (v), \
|
||||
}
|
||||
|
||||
static inline void nf7core_lua_value_push(
|
||||
const struct nf7core_lua_value* this, lua_State* L) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != L);
|
||||
|
||||
switch (this->type) {
|
||||
case NF7CORE_LUA_VALUE_TYPE_NIL:
|
||||
lua_pushnil(L);
|
||||
break;
|
||||
case NF7CORE_LUA_VALUE_TYPE_INT:
|
||||
lua_pushinteger(L, this->i);
|
||||
break;
|
||||
case NF7CORE_LUA_VALUE_TYPE_NUM:
|
||||
lua_pushnumber(L, this->n);
|
||||
break;
|
||||
case NF7CORE_LUA_VALUE_TYPE_PTR:
|
||||
assert(nullptr != this->ptr);
|
||||
nf7core_lua_value_ptr_push(this->ptr, L);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void nf7core_lua_value_unset(struct nf7core_lua_value* this) {
|
||||
assert(nullptr != this);
|
||||
|
||||
switch (this->type) {
|
||||
case NF7CORE_LUA_VALUE_TYPE_PTR:
|
||||
nf7core_lua_value_ptr_unref(this->ptr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type = NF7CORE_LUA_VALUE_TYPE_NIL;
|
||||
}
|
||||
|
||||
static inline bool nf7core_lua_value_set(
|
||||
struct nf7core_lua_value* this, const struct nf7core_lua_value* src) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != src);
|
||||
|
||||
nf7core_lua_value_unset(this);
|
||||
|
||||
switch (src->type) {
|
||||
case NF7CORE_LUA_VALUE_TYPE_PTR:
|
||||
*this = *src;
|
||||
nf7core_lua_value_ptr_ref(this->ptr);
|
||||
return true;
|
||||
default:
|
||||
*this = *src;
|
||||
return true;
|
||||
}
|
||||
}
|
64
core/lua/value_ptr.c
Normal file
64
core/lua/value_ptr.c
Normal file
@ -0,0 +1,64 @@
|
||||
// No copyright
|
||||
#include "core/lua/value_ptr.h"
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
|
||||
struct nf7core_lua_value_ptr {
|
||||
struct nf7core_lua_thread* thread;
|
||||
struct nf7util_malloc* malloc;
|
||||
lua_State* lua;
|
||||
|
||||
uint32_t refcnt;
|
||||
int index;
|
||||
};
|
||||
|
||||
static void del_(struct nf7core_lua_value_ptr*);
|
||||
|
||||
NF7UTIL_REFCNT_IMPL(, nf7core_lua_value_ptr, {del_(this);});
|
||||
|
||||
|
||||
struct nf7core_lua_value_ptr* nf7core_lua_value_ptr_new(
|
||||
struct nf7core_lua_thread* thread, lua_State* L) {
|
||||
assert(nullptr != thread);
|
||||
assert(nullptr != L);
|
||||
|
||||
struct nf7core_lua_value_ptr* this =
|
||||
nf7util_malloc_alloc(thread->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
goto ABORT;
|
||||
}
|
||||
*this = (struct nf7core_lua_value_ptr) {
|
||||
.malloc = thread->malloc,
|
||||
.lua = L,
|
||||
};
|
||||
|
||||
this->thread = thread;
|
||||
nf7core_lua_thread_ref(this->thread);
|
||||
|
||||
this->index = luaL_ref(this->lua, LUA_REGISTRYINDEX);
|
||||
|
||||
nf7core_lua_value_ptr_ref(this);
|
||||
return this;
|
||||
|
||||
ABORT:
|
||||
lua_pop(L, 1);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nf7core_lua_value_ptr_push(
|
||||
const struct nf7core_lua_value_ptr* this, lua_State* lua) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != lua);
|
||||
lua_rawgeti(lua, LUA_REGISTRYINDEX, this->index);
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_lua_value_ptr* this) {
|
||||
assert(nullptr != this);
|
||||
luaL_unref(this->lua, LUA_REGISTRYINDEX, this->index);
|
||||
nf7core_lua_thread_unref(this->thread);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
22
core/lua/value_ptr.h
Normal file
22
core/lua/value_ptr.h
Normal file
@ -0,0 +1,22 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lua.h>
|
||||
|
||||
#include "util/refcnt.h"
|
||||
|
||||
|
||||
struct nf7core_lua_thread;
|
||||
|
||||
struct nf7core_lua_value_ptr;
|
||||
NF7UTIL_REFCNT_DECL(, nf7core_lua_value_ptr);
|
||||
|
||||
struct nf7core_lua_value_ptr* nf7core_lua_value_ptr_new(
|
||||
struct nf7core_lua_thread*, lua_State*);
|
||||
// POSTCONDS:
|
||||
// - the top value is always popped
|
||||
|
||||
void nf7core_lua_value_ptr_push(
|
||||
const struct nf7core_lua_value_ptr*, lua_State*);
|
14
core/null/CMakeLists.txt
Normal file
14
core/null/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
add_library(nf7core_null)
|
||||
target_sources(nf7core_null
|
||||
PRIVATE
|
||||
idea.c
|
||||
mod.c
|
||||
PUBLIC
|
||||
mod.h
|
||||
)
|
||||
target_link_libraries(nf7core_null
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
)
|
||||
|
64
core/null/idea.c
Normal file
64
core/null/idea.c
Normal file
@ -0,0 +1,64 @@
|
||||
// No copyright
|
||||
#include <assert.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/malloc.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
#include "core/null/mod.h"
|
||||
|
||||
|
||||
static struct nf7core_exec_entity* new_(struct nf7core_exec*);
|
||||
|
||||
struct nf7core_exec_entity* nf7core_null_entity_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_exec* exec = (void*) nf7_get_mod_by_meta(nf7, &nf7core_exec);
|
||||
if (nullptr == exec) {
|
||||
nf7util_log_error("nf7core_exec module is missing");
|
||||
return nullptr;
|
||||
}
|
||||
return new_(exec);
|
||||
}
|
||||
|
||||
static struct nf7core_exec_entity* new_(struct nf7core_exec* mod) {
|
||||
assert(nullptr != mod);
|
||||
|
||||
const struct nf7* nf7 = mod->super.nf7;
|
||||
|
||||
struct nf7core_exec_entity* this =
|
||||
nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_exec_entity) {
|
||||
.idea = &nf7core_null_idea,
|
||||
.mod = mod,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_exec_entity* this) {
|
||||
if (nullptr != this) {
|
||||
assert(nullptr != this->mod);
|
||||
|
||||
const struct nf7* nf7 = this->mod->super.nf7;
|
||||
assert(nullptr != nf7);
|
||||
assert(nullptr != nf7->malloc);
|
||||
|
||||
nf7util_malloc_free(nf7->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_(struct nf7core_exec_entity*, struct nf7util_buffer*) { }
|
||||
|
||||
const struct nf7core_exec_idea nf7core_null_idea = {
|
||||
.name = (const uint8_t*) "nf7core_null_idea",
|
||||
.details = (const uint8_t*) "null implementation of an idea",
|
||||
|
||||
.new = new_,
|
||||
.del = del_,
|
||||
.send = send_,
|
||||
};
|
41
core/null/mod.c
Normal file
41
core/null/mod.c
Normal file
@ -0,0 +1,41 @@
|
||||
// No copyright
|
||||
#include "core/null/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/malloc.h"
|
||||
|
||||
|
||||
struct nf7_mod* nf7core_null_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7_mod* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*this = (struct nf7_mod) {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_null,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* this) {
|
||||
if (nullptr != this) {
|
||||
assert(nullptr != this->nf7);
|
||||
assert(nullptr != this->nf7->malloc);
|
||||
|
||||
nf7util_malloc_free(this->nf7->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_null = {
|
||||
.name = (const uint8_t*) "nf7core_null",
|
||||
.desc = (const uint8_t*) "null implementations of each interfaces",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user