Compare commits

...

No commits in common. "v0.2.0" and "main" have entirely different histories.
v0.2.0 ... main

161 changed files with 3646 additions and 17198 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
/build/
/build*/

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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