Compare commits

...

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

177 changed files with 3639 additions and 21993 deletions

2
.gitignore vendored
View File

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

View File

@ -1,198 +1,54 @@
cmake_minimum_required(VERSION 3.18)
cmake_policy(SET 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)
option(NF7_SANITIZE "use various sanitizer" OFF)
option(NF7_PROFILE "profiler" OFF)
set(CMAKE_C_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (NF7_SANITIZE_THREAD AND NF7_PROFILE)
message(FATAL_ERROR "NF7_SANITIZE_THREAD cannot be enabled with NF7_PROFILE")
endif()
if (NF7_SANITIZE AND NF7_SANITIZE_THREAD)
message(FATAL_ERROR "NF7_SANITIZE_THREAD cannot be enabled with NF7_SANITIZE")
endif()
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)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
)
endif()
if (NF7_SANITIZE_THREAD)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=thread -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}
$<$<CXX_COMPILER_ID:MSVC>:/bigobj>
)
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/aggregate_promise.hh
common/audio_queue.hh
common/config.hh
common/context_owner.hh
common/dir.hh
common/dir_item.hh
common/factory.hh
common/file_base.hh
common/font_queue.hh
common/future.hh
common/generic_config.hh
common/generic_context.hh
common/generic_dir.hh
common/generic_history.hh
common/generic_memento.hh
common/generic_type_info.hh
common/generic_watcher.hh
common/gl_enum.hh
common/gl_fence.hh
common/gl_obj.hh
common/gl_obj.cc
common/gl_shader_preproc.hh
common/gui.hh
common/gui.cc
common/gui_dnd.hh
common/gui_timeline.hh
common/gui_timeline.cc
common/gui_value.hh
common/gui_value.cc
common/gui_window.hh
common/gui_window.cc
common/history.hh
common/life.hh
common/logger.hh
common/logger_ref.hh
common/luajit.hh
common/luajit.cc
common/luajit_nfile_importer.hh
common/luajit_queue.hh
common/luajit_ref.hh
common/luajit_thread.hh
common/luajit_thread.cc
common/memento.hh
common/memento_recorder.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/ptr_selector.hh
common/pure_node_file.hh
common/queue.hh
common/ring_buffer.hh
common/sequencer.hh
common/squashed_history.hh
common/stopwatch.hh
common/task.hh
common/thread.hh
common/timed_queue.hh
common/util_algorithm.hh
common/value.hh
common/yaml_nf7.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/codec_stbimage.cc
file/font_context.cc
file/font_face.cc
file/gl_obj.cc
file/luajit_context.cc
file/luajit_node.cc
file/node_exprtk.cc
file/node_mutex.cc
file/node_network.cc
file/node_ref.cc
file/node_singleton.cc
file/node_ziptie.cc
file/sequencer_adaptor.cc
file/sequencer_call.cc
file/sequencer_timeline.cc
file/system_dir.cc
file/system_event.cc
file/system_imgui.cc
file/system_logger.cc
file/system_nfile.cc
file/system_node.cc
file/value_curve.cc
file/value_imm.cc
file/value_plot.cc
)
target_sources(nf7 PRIVATE main.c)
target_link_libraries(nf7
PRIVATE
exprtk
freetype
glew
glfw
imgui
imnodes
implot
linalg.h
luajit
magic_enum
miniaudio
source_location
stb
TracyClient
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,67 +1,19 @@
nf7
Nf7
====
portable visual programming platform
Nf7 is an abstraction layer for Media-programming.
The goal is to allow creative activities on any platforms without any differences.
## REQUIREMENTS
**UNDER REFACTORING:** Please checkout tag v0.4.1 to see a codebase which works.
- **OS**: Windows 10, Linux, and Chrome OS
- **CPU**: x86_64
- **GPU**: OpenGL 3.3 or higher (except Intel HD Graphics)
- **RAM**: 512 MB or more (depends on what you do)
- **Storage**: 100 MB or more (depends on what you do)
## Build
## INSTALLING
Build Nf7 by yourself, or download the binary from releases (unavailable on mirror repo).
It's expected to copy and put the executable on each of your projects to prevent old works from corruption by Nf7's update.
## BUILDING
### Succeeded Environments
- Windows 10 / CMake 3.20.21032501-MSVC_2 / cl 19.29.30137
- Arch Linux / CMake 3.24.2 / g++ 12.2.0
- Ubuntu (Chrome OS) / CMake 3.18.4 / g++ 10.2.1
### Windows
```
PS> mkdir build
PS> cd build
PS> cmake .. # add build options before the double dots
PS> cmake --build . --config Release
```bash
$ cmake -B build
$ cmake --build build -j4
```
### Linux
## License
```
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release .. # add build options before the double dots
$ make
```
WTFPLv2
### CMake build options
|name|default|description|
|--|--|--|
| `NF7_STATIC` | `ON` | links all libraries statically |
| `NF7_SANITIZE_THREAD` | `OFF` | enables thread-sanitizer (g++ only) |
| `NF7_SANITIZE` | `OFF` | enables address/undefined/leak sanitizers (g++ only) |
| `NF7_PROFILE` | `OFF` | enables Tracy features |
The following condition must be met:
```
!(NF7_SANITIZE_THREAD && NF7_SANITIZE) && // address-sanitizer cannot be with thread-sanitizer
!(NF7_SANITIZE_THREAD && NF7_PROFILE) // TracyClient causes error because of thread-sanitizer
```
## DEPENDENCIES
see `thirdparty/CMakeLists.txt`
## LICENSE
Do What The Fuck You Want To Public License v2
*-- expression has nothing without the real free --*

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,78 +0,0 @@
#pragma once
#include <memory>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
class AggregatePromise final :
public std::enable_shared_from_this<AggregatePromise> {
public:
AggregatePromise() = delete;
AggregatePromise(const std::shared_ptr<nf7::Context>& ctx) noexcept :
data_(std::make_shared<Data>(ctx)) {
data_->Ref();
}
~AggregatePromise() noexcept {
data_->Unref();
}
AggregatePromise(const AggregatePromise&) = delete;
AggregatePromise(AggregatePromise&&) = delete;
AggregatePromise& operator=(const AggregatePromise&) = delete;
AggregatePromise& operator=(AggregatePromise&&) = delete;
AggregatePromise& Add(auto fu) noexcept {
data_->Ref();
fu.Then([data = data_](auto& fu) {
try {
fu.value();
data->Unref();
} catch (nf7::Exception&) {
data->Abort(std::current_exception());
}
});
return *this;
}
nf7::Future<std::monostate> future() noexcept {
return data_->future();
}
private:
struct Data {
public:
Data() = delete;
Data(const std::shared_ptr<nf7::Context>& ctx) noexcept :
pro_(ctx) {
}
void Ref() noexcept {
++refcnt_;
}
void Unref() noexcept {
if (0 == --refcnt_) {
pro_.Return({});
}
}
void Abort(std::exception_ptr e) noexcept {
pro_.Throw(e);
}
nf7::Future<std::monostate> future() const noexcept {
return pro_.future();
}
private:
nf7::Future<std::monostate>::Promise pro_;
std::atomic<size_t> refcnt_ = 0;
};
std::shared_ptr<Data> data_;
};
} // namespace nf7

View File

@ -1,30 +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
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,22 +0,0 @@
#pragma once
#include <string>
#include "nf7.hh"
namespace nf7 {
class Config : public nf7::File::Interface {
public:
Config() = default;
Config(const Config&) = delete;
Config(Config&&) = delete;
Config& operator=(const Config&) = delete;
Config& operator=(Config&&) = delete;
virtual std::string Stringify() const noexcept = 0;
virtual void Parse(const std::string&) = 0;
};
} // namespace nf7

View File

@ -1,50 +0,0 @@
#pragma once
#include <algorithm>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "nf7.hh"
namespace nf7 {
class ContextOwner final {
public:
ContextOwner() = default;
~ContextOwner() noexcept {
AbortAll();
}
ContextOwner(const ContextOwner&) = delete;
ContextOwner(ContextOwner&&) = default;
ContextOwner& operator=(const ContextOwner&) = delete;
ContextOwner& operator=(ContextOwner&&) = default;
template <typename T, typename... Args>
std::shared_ptr<T> Create(Args&&... args) noexcept {
static_assert(std::is_base_of<nf7::Context, T>::value);
ctx_.erase(
std::remove_if(ctx_.begin(), ctx_.end(), [](auto& x) { return x.expired(); }),
ctx_.end());
auto ret = std::make_shared<T>(std::forward<Args>(args)...);
ctx_.emplace_back(ret);
return ret;
}
void AbortAll() noexcept {
for (auto& wctx : ctx_) {
if (auto ctx = wctx.lock()) {
ctx->Abort();
}
}
}
private:
std::vector<std::weak_ptr<nf7::Context>> ctx_;
};
} // namespace nf7

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,49 +0,0 @@
#pragma once
#include <cstdint>
#include "nf7.hh"
namespace nf7 {
class DirItem : public File::Interface {
public:
enum Flag : uint16_t {
kNone = 0,
kTree = 1 << 0,
kMenu = 1 << 1,
kTooltip = 1 << 2,
kWidget = 1 << 3,
kDragDropTarget = 1 << 4,
// Update() will be called earlier than other items.
// This is used by some system files and meaningless in most of cases.
kEarlyUpdate = 1 << 5,
// suggests to forbid to move/remove/clone through UI
kImportant = 1 << 6,
};
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,27 +0,0 @@
#pragma once
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
template <typename T>
class Factory : public nf7::File::Interface {
public:
using Product = T;
Factory() = default;
Factory(const Factory&) = delete;
Factory(Factory&&) = delete;
Factory& operator=(const Factory&) = delete;
Factory& operator=(Factory&&) = delete;
virtual Product Create() noexcept = 0;
};
template <typename T>
using AsyncFactory = Factory<nf7::Future<T>>;
} // namespace nf7

View File

@ -1,72 +0,0 @@
#pragma once
#include <algorithm>
#include <string_view>
#include <vector>
#include "nf7.hh"
namespace nf7 {
class FileBase : public nf7::File {
public:
class Feature {
public:
Feature() = delete;
Feature(nf7::FileBase& f) noexcept {
f.feats_.push_back(this);
}
virtual ~Feature() = default;
Feature(const Feature&) = delete;
Feature(Feature&&) = delete;
Feature& operator=(const Feature&) = delete;
Feature& operator=(Feature&&) = delete;
virtual nf7::File* Find(std::string_view) const noexcept { return nullptr; }
virtual void Handle(const nf7::File::Event&) noexcept { }
virtual void Update() noexcept { }
};
using nf7::File::File;
nf7::File* Find(std::string_view name) const noexcept final {
if (auto ret = PreFind(name)) {
return ret;
}
for (auto feat : feats_) {
if (auto ret = feat->Find(name)) {
return ret;
}
}
return nullptr;
}
void Handle(const nf7::File::Event& ev) noexcept final {
PreHandle(ev);
for (auto feat : feats_) {
feat->Handle(ev);
}
PostHandle(ev);
}
void Update() noexcept final {
PreUpdate();
for (auto feat : feats_) {
feat->Update();
}
PostUpdate();
}
protected:
virtual nf7::File* PreFind(std::string_view) const noexcept { return nullptr; }
virtual void PreHandle(const nf7::File::Event&) noexcept { }
virtual void PostHandle(const nf7::File::Event&) noexcept { }
virtual void PreUpdate() noexcept { }
virtual void PostUpdate() noexcept { }
private:
std::vector<Feature*> feats_;
};
} // namespace nf7

View File

@ -1,57 +0,0 @@
#pragma once
#include <exception>
#include <memory>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "nf7.hh"
#include "common/font_queue.hh"
#include "common/future.hh"
namespace nf7::font {
class Face final {
public:
static nf7::Future<std::shared_ptr<Face>> Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::font::Queue>& q,
const std::filesystem::path& path) noexcept {
nf7::Future<std::shared_ptr<Face>>::Promise pro {ctx};
q->Push(ctx, [=](auto ft) mutable {
try {
FT_Face face;
font::Enforce(FT_New_Face(ft, path.generic_string().c_str(), 0, &face));
pro.Return(std::make_shared<Face>(ctx, q, face));
} catch (nf7::Exception&) {
pro.Throw(std::current_exception());
}
});
return pro.future();
}
Face(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::font::Queue>& q,
FT_Face face) noexcept :
ctx_(ctx), q_(q), face_(face) {
}
~Face() noexcept {
q_->Push(ctx_, [face = face_](auto) {
FT_Done_Face(face);
});
}
FT_Face operator*() const noexcept { return face_; }
const std::shared_ptr<nf7::font::Queue>& ftq() const noexcept { return q_; }
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::font::Queue> q_;
FT_Face face_;
};
} // namespace nf7::font

View File

@ -1,41 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "nf7.hh"
namespace nf7::font {
class Queue : public nf7::File::Interface {
public:
using Task = std::function<void(FT_Library)>;
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&&) noexcept = 0;
virtual std::shared_ptr<Queue> self() noexcept = 0;
};
inline void Enforce(FT_Error e) {
if (e == 0) return;
# undef FTERRORS_H_
# define FT_ERROR_START_LIST switch (e) {
# define FT_ERRORDEF(e, v, s) case e: throw nf7::Exception {s};
# define FT_ERROR_END_LIST default: throw nf7::Exception {"unknown freetype error"};}
# include FT_ERRORS_H
# undef FT_ERROR_START_LIST
# undef FT_ERRORDEF
# undef FT_ERROR_END_LIST
}
} // namespace nf7::font

View File

@ -1,417 +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 Product = T;
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 in the executor.
ThisFuture& Then(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const ThisFuture&)>&& f) noexcept {
auto fun = std::move(f);
if (ctx) {
fun = [exec, ctx, fun = std::move(fun)](auto& fu) {
ctx->env().Exec(
exec, 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(nf7::Env::Executor exec,
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(exec, ctx, [pro, f = std::move(f)](auto& fu) mutable {
try {
f(fu, pro);
} catch (...) {
pro.Throw(std::current_exception());
}
});
return pro.future();
}
template <typename F>
ThisFuture& Then(const std::shared_ptr<nf7::Context>& ctx, F&& f) noexcept {
return Then(nf7::Env::kSub, ctx, std::move(f));
}
template <typename F>
ThisFuture& Then(F&& f) noexcept {
return Then(nullptr, std::move(f));
}
// same as Then() but called when it's done without error
ThisFuture& ThenIf(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const T&)>&& f) noexcept {
Then(exec, ctx, [f = std::move(f)](auto& fu) {
if (fu.done()) f(fu.value());
});
return *this;
}
ThisFuture& ThenIf(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(const T&)>&& f) noexcept {
return ThenIf(nf7::Env::kSub, ctx, std::move(f));
}
ThisFuture& ThenIf(std::function<void(const T&)>&& f) noexcept {
return ThenIf(nullptr, std::move(f));
}
// same as Then() but called when it caused an exception
template <typename E>
ThisFuture& Catch(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
std::function<void(E&)>&& f) noexcept {
Then(exec, ctx, [f = std::move(f)](auto& fu) {
try { fu.value(); } catch (E& e) { f(e); } catch (...) { }
});
return *this;
}
template <typename E>
ThisFuture& Catch(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(E&)>&& f) noexcept {
return Catch<E>(nf7::Env::kSub, ctx, std::move(f));
}
template <typename E>
ThisFuture& Catch(std::function<void(E&)>&& f) noexcept {
return Catch<E>(nullptr, std::move(f));
}
// Finalizes the other promise on finalize of this future.
template <typename P, typename F>
ThisFuture& Chain(nf7::Env::Executor exec,
const std::shared_ptr<nf7::Context>& ctx,
P& pro, F&& func) noexcept {
return Then(exec, ctx, [pro, func = std::move(func)](auto& fu) mutable {
try {
if constexpr (std::is_void<decltype(func(fu.value()))>::value) {
func(fu.value());
} else {
pro.Return(func(fu.value()));
}
} catch (...) {
pro.Throw(std::current_exception());
}
});
}
template <typename P, typename F>
ThisFuture& Chain(const std::shared_ptr<nf7::Context>& ctx, P& pro, F&& func) noexcept {
return Chain(nf7::Env::kSub, ctx, pro, std::move(func));
}
template <typename P, typename F>
ThisFuture& Chain(P& pro, F&& func) noexcept {
return Chain(nullptr, pro, std::move(func));
}
template <typename P>
ThisFuture& Chain(P& pro) noexcept {
return Chain(pro, [](auto& v) { return v; });
}
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_ && data_->state == kDone);
}
bool error() const noexcept {
return
(imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
(data_ && 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,52 +0,0 @@
#pragma once
#include <functional>
#include <string>
#include "nf7.hh"
#include "common/config.hh"
#include "common/generic_memento.hh"
namespace nf7 {
template <typename T>
concept ConfigData = requires (T& x) {
{ x.Stringify() } -> std::convertible_to<std::string>;
x.Parse(std::string {});
};
class GenericConfig : public nf7::Config {
public:
GenericConfig() = delete;
template <ConfigData T>
GenericConfig(nf7::GenericMemento<T>& mem) noexcept {
stringify_ = [&mem]() {
return mem->Stringify();
};
parse_ = [&mem](auto& str) {
mem->Parse(str);
mem.Commit();
};
}
GenericConfig(const GenericConfig&) = delete;
GenericConfig(GenericConfig&&) = delete;
GenericConfig& operator=(const GenericConfig&) = delete;
GenericConfig& operator=(GenericConfig&&) = delete;
std::string Stringify() const noexcept override {
return stringify_();
}
void Parse(const std::string& str) override {
parse_(str);
}
private:
std::function<std::string()> stringify_;
std::function<void(const std::string&)> parse_;
};
} // namespace nf7

View File

@ -1,55 +0,0 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include "nf7.hh"
namespace nf7 {
class GenericContext : public nf7::Context {
public:
GenericContext(nf7::Env& env,
nf7::File::Id id,
std::string_view desc = "",
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
nf7::Context(env, id, parent), desc_(desc) {
}
GenericContext(nf7::File& f,
std::string_view desc = "",
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
GenericContext(f.env(), f.id(), desc, parent) {
}
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,173 +0,0 @@
#pragma once
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <yas/serialize.hpp>
#include <yas/types/std/map.hpp>
#include <tracy/Tracy.hpp>
#include "nf7.hh"
#include "common/dir.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/yas_nf7.hh"
namespace nf7 {
class GenericDir : public nf7::FileBase::Feature, public nf7::Dir {
public:
using ItemMap = std::map<std::string, std::unique_ptr<nf7::File>>;
GenericDir() = delete;
GenericDir(nf7::FileBase& f, ItemMap&& items = {}) noexcept :
nf7::FileBase::Feature(f), f_(f), items_(std::move(items)) {
}
GenericDir(const GenericDir&) = delete;
GenericDir(GenericDir&&) = delete;
GenericDir& operator=(const GenericDir&) = delete;
GenericDir& operator=(GenericDir&&) = delete;
void Serialize(auto& ar) const noexcept {
ar(items_);
}
void Deserialize(auto& ar) {
assert(f_.id() == 0);
assert(items_.size() == 0);
size_t n;
ar(n);
for (size_t i = 0; i < n; ++i) {
std::string k;
try {
std::unique_ptr<nf7::File> f;
ar(k, f);
nf7::File::Path::ValidateTerm(k);
if (items_.end() != items_.find(k)) {
throw nf7::Exception {"item name duplicated"};
}
items_.emplace(k, std::move(f));
} catch (nf7::Exception&) {
f_.env().Throw(std::make_exception_ptr(
nf7::Exception {"failed to deserialize item: "+k}));
}
}
}
ItemMap CloneItems(nf7::Env& env) const {
ItemMap ret;
for (auto& p : items_) {
ret[p.first] = p.second->Clone(env);
}
return ret;
}
std::string GetUniqueName(std::string_view name) const noexcept {
auto ret = std::string {name};
while (items_.end() != items_.find(ret)) {
ret += "_dup";
}
return ret;
}
nf7::File* Find(std::string_view name) const noexcept override {
auto itr = items_.find(std::string {name});
return itr != items_.end()? itr->second.get(): nullptr;
}
nf7::File& Add(std::string_view name, std::unique_ptr<File>&& f) override {
const auto sname = std::string(name);
auto [itr, ok] = items_.emplace(sname, std::move(f));
if (!ok) throw nf7::Dir::DuplicateException {"item name duplication: "+sname};
auto& ret = *itr->second;
if (f_.id()) ret.MoveUnder(f_, name);
return ret;
}
std::unique_ptr<nf7::File> Remove(std::string_view name) noexcept override {
auto itr = items_.find(std::string(name));
if (itr == items_.end()) return nullptr;
auto ret = std::move(itr->second);
items_.erase(itr);
if (f_.id()) ret->Isolate();
return ret;
}
nf7::File* Rename(std::string_view before, std::string_view after) noexcept {
if (auto f = Remove(before)) {
return &Add(after, std::move(f));
} else {
return nullptr;
}
}
nf7::File* Renew(std::string_view name) noexcept {
return Rename(name, name);
}
const ItemMap& items() const noexcept { return items_; }
private:
nf7::FileBase& f_;
ItemMap items_;
void Update() noexcept override {
UpdateChildren(true);
UpdateChildren(false);
}
void UpdateChildren(bool early) noexcept {
for (auto& p : items_) {
ZoneScopedN("update child");
ZoneText(p.first.data(), p.first.size());
auto& f = *p.second;
auto* ditem = f.interface<nf7::DirItem>();
const bool e = ditem && (ditem->flags() & nf7::DirItem::kEarlyUpdate);
if (e == early) f.Update();
}
}
void Handle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
for (auto& p : items_) p.second->MoveUnder(f_, p.first);
return;
case nf7::File::Event::kRemove:
for (auto& p : items_) p.second->Isolate();
return;
default:
return;
}
}
};
} // namespace nf7
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::GenericDir> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::GenericDir& dir) {
dir.Serialize(ar);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::GenericDir& dir) {
dir.Deserialize(ar);
return ar;
}
};
} // namespace yas::detail

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,127 +0,0 @@
#pragma once
#include <cassert>
#include <memory>
#include <unordered_map>
#include <utility>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_context.hh"
#include "common/memento.hh"
namespace nf7 {
template <typename T>
class GenericMemento : public nf7::FileBase::Feature, public nf7::Memento {
public:
class CustomTag;
GenericMemento(nf7::FileBase& f, T&& data) noexcept :
nf7::FileBase::Feature(f),
file_(f), initial_(T(data)), data_(std::move(data)) {
}
~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();
file_.Touch();
}
void Commit(bool quiet = false) noexcept {
tag_ = nullptr;
onCommit();
if (!quiet) file_.Touch();
}
void CommitQuiet() noexcept {
Commit(true);
}
void CommitAmend(bool quiet = false) noexcept {
if (!tag_) return;
auto itr = map_.find(tag_->id());
assert(itr != map_.end());
itr->second = data_;
onCommit();
if (!quiet) file_.Touch();
}
void CommitAmendQuiet() noexcept {
CommitAmend(true);
}
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& 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_;
void Handle(const nf7::File::Event& e) noexcept override {
switch (e.type) {
case nf7::File::Event::kAdd:
file_.env().ExecMain(
std::make_shared<nf7::GenericContext>(file_),
[this]() { CommitQuiet(); });
return;
default:
return;
}
}
};
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,60 +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>
class GenericTypeInfo : public nf7::File::TypeInfo {
public:
GenericTypeInfo(const std::string& name,
std::unordered_set<std::string>&& v,
const std::string& desc = "(no description)") noexcept :
TypeInfo(name, AddFlags(std::move(v))), desc_(desc) {
}
std::unique_ptr<nf7::File> Deserialize(nf7::Deserializer& ar) const override
try {
if constexpr (std::is_constructible<T, nf7::Deserializer&>::value) {
return std::make_unique<T>(ar);
} else {
throw nf7::Exception {name() + " is not a deserializable"};
}
} catch (std::exception&) {
throw nf7::DeserializeException {"deserialization failed ("+name()+")"};
}
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 default factory"};
}
}
void UpdateTooltip() const noexcept override {
ImGui::TextUnformatted(desc_.c_str());
}
private:
std::string desc_;
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,327 +0,0 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <unordered_map>
#include <GL/glew.h>
#include <magic_enum.hpp>
#include "nf7.hh"
#include "common/yas_enum.hh"
namespace nf7::gl {
template <typename T>
struct EnumMeta {};
template <typename T>
GLenum ToEnum(T v) noexcept {
assert(EnumMeta<T>::glmap.contains(v));
return EnumMeta<T>::glmap.find(v)->second;
}
template <typename T>
GLenum ToEnum(const std::string& v)
try {
return ToEnum(magic_enum::enum_cast<T>(v).value());
} catch (std::bad_optional_access&) {
throw nf7::Exception {"unknown enum: "+v};
}
enum class NumericType : uint8_t {
U8 = 0x01,
I8 = 0x11,
U16 = 0x02,
I16 = 0x12,
U32 = 0x04,
I32 = 0x14,
F16 = 0x22,
F32 = 0x24,
F64 = 0x28,
};
template <>
struct EnumMeta<NumericType> {
static inline const std::unordered_map<NumericType, GLenum> glmap = {
{NumericType::U8, GL_UNSIGNED_BYTE},
{NumericType::I8, GL_BYTE},
{NumericType::U16, GL_UNSIGNED_SHORT},
{NumericType::I16, GL_SHORT},
{NumericType::U32, GL_UNSIGNED_INT},
{NumericType::I32, GL_INT},
{NumericType::F16, GL_HALF_FLOAT},
{NumericType::F32, GL_FLOAT},
{NumericType::F64, GL_DOUBLE},
};
};
inline uint8_t GetByteSize(NumericType t) noexcept {
return magic_enum::enum_integer(t) & 0xF;
}
enum class ColorComp : uint8_t {
R = 0x01,
G = 0x11,
B = 0x21,
RG = 0x02,
RGB = 0x03,
RGBA = 0x04,
};
template <>
struct EnumMeta<ColorComp> {
static inline const std::unordered_map<ColorComp, GLenum> glmap = {
{ColorComp::R, GL_RED},
{ColorComp::G, GL_GREEN},
{ColorComp::B, GL_BLUE},
{ColorComp::RG, GL_RG},
{ColorComp::RGB, GL_RGB},
{ColorComp::RGBA, GL_RGBA},
};
};
inline uint8_t GetCompCount(ColorComp c) noexcept {
return magic_enum::enum_integer(c) & 0xF;
}
enum class InternalFormat : uint8_t {
R8 = 0x01,
RG8 = 0x02,
RGB8 = 0x03,
RGBA8 = 0x04,
RF32 = 0x11,
RGF32 = 0x12,
RGBF32 = 0x13,
RGBAF32 = 0x14,
Depth16 = 0x21,
Depth24 = 0x31,
DepthF32 = 0x41,
Depth24_Stencil8 = 0x22,
DepthF32_Stencil8 = 0x32,
};
template <>
struct EnumMeta<InternalFormat> {
static inline const std::unordered_map<InternalFormat, GLenum> glmap = {
{InternalFormat::R8, GL_R8},
{InternalFormat::RG8, GL_RG8},
{InternalFormat::RGB8, GL_RGB8},
{InternalFormat::RGBA8, GL_RGBA8},
{InternalFormat::RF32, GL_R32F},
{InternalFormat::RGF32, GL_RG32F},
{InternalFormat::RGBF32, GL_RGB32F},
{InternalFormat::RGBAF32, GL_RGBA32F},
{InternalFormat::Depth16, GL_DEPTH_COMPONENT16},
{InternalFormat::Depth24, GL_DEPTH_COMPONENT24},
{InternalFormat::DepthF32, GL_DEPTH_COMPONENT32F},
{InternalFormat::Depth24_Stencil8, GL_DEPTH24_STENCIL8},
{InternalFormat::DepthF32_Stencil8, GL_DEPTH32F_STENCIL8},
};
};
inline uint8_t GetByteSize(InternalFormat fmt) noexcept {
switch (fmt) {
case InternalFormat::R8: return 1;
case InternalFormat::RG8: return 2;
case InternalFormat::RGB8: return 3;
case InternalFormat::RGBA8: return 4;
case InternalFormat::RF32: return 4;
case InternalFormat::RGF32: return 8;
case InternalFormat::RGBF32: return 12;
case InternalFormat::RGBAF32: return 16;
case InternalFormat::Depth16: return 2;
case InternalFormat::Depth24: return 3;
case InternalFormat::DepthF32: return 4;
case InternalFormat::Depth24_Stencil8: return 4;
case InternalFormat::DepthF32_Stencil8: return 5;
}
std::abort();
}
inline ColorComp GetColorComp(InternalFormat fmt) {
switch (fmt) {
case InternalFormat::R8: return ColorComp::R;
case InternalFormat::RG8: return ColorComp::RG;
case InternalFormat::RGB8: return ColorComp::RGB;
case InternalFormat::RGBA8: return ColorComp::RGBA;
case InternalFormat::RF32: return ColorComp::R;
case InternalFormat::RGF32: return ColorComp::RG;
case InternalFormat::RGBF32: return ColorComp::RGB;
case InternalFormat::RGBAF32: return ColorComp::RGBA;
default: throw nf7::Exception {"does not have color component"};
}
}
inline NumericType GetNumericType(InternalFormat fmt) {
switch (fmt) {
case InternalFormat::R8:
case InternalFormat::RG8:
case InternalFormat::RGB8:
case InternalFormat::RGBA8:
return NumericType::U8;
case InternalFormat::RF32:
case InternalFormat::RGF32:
case InternalFormat::RGBF32:
case InternalFormat::RGBAF32:
return NumericType::F32;
default:
throw nf7::Exception {"does not have color component"};
}
}
inline bool IsColor(InternalFormat fmt) noexcept {
return (magic_enum::enum_integer(fmt) & 0xF0) <= 1;
}
inline bool HasDepth(InternalFormat fmt) noexcept {
return !IsColor(fmt);
}
inline bool HasStencil(InternalFormat fmt) noexcept {
return
fmt == InternalFormat::Depth24_Stencil8 ||
fmt == InternalFormat::DepthF32_Stencil8;
}
enum class TestFunc {
Never,
Always,
Equal,
NotEqual,
Less,
LessOrEqual,
Greater,
GreaterOrEqual,
};
template <>
struct EnumMeta<TestFunc> {
static inline const std::unordered_map<TestFunc, GLenum> glmap = {
{TestFunc::Never, GL_NEVER},
{TestFunc::Always, GL_ALWAYS},
{TestFunc::Equal, GL_EQUAL},
{TestFunc::NotEqual, GL_NOTEQUAL},
{TestFunc::Less, GL_LESS},
{TestFunc::LessOrEqual, GL_LEQUAL},
{TestFunc::Greater, GL_GREATER},
{TestFunc::GreaterOrEqual, GL_GEQUAL},
};
};
enum class BufferTarget {
Array,
ElementArray,
};
template <>
struct EnumMeta<BufferTarget> {
static inline const std::unordered_map<BufferTarget, GLenum> glmap = {
{BufferTarget::Array, GL_ARRAY_BUFFER},
{BufferTarget::ElementArray, GL_ELEMENT_ARRAY_BUFFER},
};
};
enum class BufferUsage {
StaticDraw,
DynamicDraw,
StreamDraw,
StaticRead,
DynamicRead,
StreamRead,
StaticCopy,
DynamicCopy,
StreamCopy,
};
template <>
struct EnumMeta<BufferUsage> {
static inline const std::unordered_map<BufferUsage, GLenum> glmap = {
{BufferUsage::StaticDraw, GL_STATIC_DRAW},
{BufferUsage::DynamicDraw, GL_DYNAMIC_DRAW},
{BufferUsage::DynamicDraw, GL_STREAM_DRAW},
{BufferUsage::StaticRead, GL_STATIC_READ},
{BufferUsage::DynamicRead, GL_DYNAMIC_READ},
{BufferUsage::DynamicRead, GL_STREAM_READ},
{BufferUsage::StaticCopy, GL_STATIC_COPY},
{BufferUsage::DynamicCopy, GL_DYNAMIC_COPY},
{BufferUsage::DynamicCopy, GL_STREAM_COPY},
};
};
enum class TextureTarget : uint8_t {
Tex2D = 0x02,
Rect = 0x12,
};
template <>
struct EnumMeta<TextureTarget> {
static inline const std::unordered_map<TextureTarget, GLenum> glmap = {
{TextureTarget::Tex2D, GL_TEXTURE_2D},
{TextureTarget::Rect, GL_TEXTURE_RECTANGLE},
};
};
inline uint8_t GetDimension(TextureTarget t) noexcept {
return magic_enum::enum_integer(t) & 0xF;
}
enum class ShaderType {
Vertex,
Fragment,
};
template <>
struct EnumMeta<ShaderType> {
static inline const std::unordered_map<ShaderType, GLenum> glmap = {
{ShaderType::Vertex, GL_VERTEX_SHADER},
{ShaderType::Fragment, GL_FRAGMENT_SHADER},
};
};
enum class DrawMode {
Points,
LineStrip,
LineLoop,
Lines,
LineStripAdjacency,
LinesAdjacency,
TriangleStrip,
TriangleFan,
Triangles,
TriangleStripAdjacency,
TrianglesAdjacency,
};
template <>
struct EnumMeta<DrawMode> {
static inline const std::unordered_map<DrawMode, GLenum> glmap = {
{DrawMode::Points, GL_POINTS},
{DrawMode::LineStrip, GL_LINE_STRIP},
{DrawMode::LineLoop, GL_LINE_LOOP},
{DrawMode::Lines, GL_LINES},
{DrawMode::LineStripAdjacency, GL_LINE_STRIP_ADJACENCY},
{DrawMode::LinesAdjacency, GL_LINES_ADJACENCY},
{DrawMode::TriangleStrip, GL_TRIANGLE_STRIP},
{DrawMode::TriangleFan, GL_TRIANGLE_FAN},
{DrawMode::Triangles, GL_TRIANGLES},
{DrawMode::TriangleStripAdjacency, GL_TRIANGLE_STRIP_ADJACENCY},
{DrawMode::TrianglesAdjacency, GL_TRIANGLES_ADJACENCY},
};
};
} // namespace nf7::gl
namespace yas::detail {
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::NumericType);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::ColorComp);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::InternalFormat);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::TestFunc);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::BufferTarget);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::BufferUsage);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::TextureTarget);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::ShaderType);
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::gl::DrawMode);
} // namespace yas::detail

View File

@ -1,44 +0,0 @@
#pragma once
#include <chrono>
#include <memory>
#include <GL/glew.h>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7::gl {
inline void Await(const std::shared_ptr<nf7::Context>& ctx,
nf7::Future<std::monostate>::Promise& pro,
GLsync sync) noexcept {
const auto state = glClientWaitSync(sync, 0, 0);
assert(0 == glGetError());
if (state == GL_ALREADY_SIGNALED || state == GL_CONDITION_SATISFIED) {
glDeleteSync(sync);
pro.Return({});
} else {
ctx->env().ExecGL(ctx, [ctx, pro, sync]() mutable {
Await(ctx, pro, sync);
}, nf7::Env::Clock::now() + std::chrono::milliseconds(10));
}
}
// The returned future will be finalized on GL thread.
inline nf7::Future<std::monostate> ExecFenceSync(
const std::shared_ptr<nf7::Context>& ctx) noexcept {
nf7::Future<std::monostate>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [ctx, pro]() mutable {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
assert(0 == glGetError());
Await(ctx, pro, sync);
});
return pro.future();
}
} // namespace nf7::gl

View File

@ -1,457 +0,0 @@
#include "common/gl_obj.hh"
#include <algorithm>
#include <array>
#include <exception>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <unordered_set>
#include <vector>
#include <tracy/Tracy.hpp>
#include "common/aggregate_promise.hh"
#include "common/factory.hh"
#include "common/future.hh"
#include "common/gl_enum.hh"
#include "common/mutex.hh"
namespace nf7::gl {
template <typename T>
auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
typename T::Factory& factory,
std::function<void(const T&)>&& validator) noexcept {
typename nf7::Future<nf7::Mutex::Resource<std::shared_ptr<T>>>::Promise pro {ctx};
factory.Create().Chain(pro, [validator](auto& v) {
validator(**v);
return v;
});
return pro.future();
}
static auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
nf7::gl::Buffer::Factory& factory,
nf7::gl::BufferTarget target,
size_t required) noexcept {
return LockAndValidate<gl::Buffer>(ctx, factory, [target, required](auto& buf) {
if (buf.meta().target != target) {
throw nf7::Exception {"incompatible buffer target"};
}
const auto size = buf.param().size;
if (size < required) {
std::stringstream st;
st << "buffer shortage (" << size << "/" << required << ")";
throw nf7::Exception {st.str()};
}
});
}
static auto LockAndValidate(const std::shared_ptr<nf7::Context>& ctx,
nf7::gl::Texture::Factory& factory,
nf7::gl::TextureTarget target) noexcept {
return LockAndValidate<gl::Texture>(ctx, factory, [target](auto& tex) {
if (tex.meta().target != target) {
throw nf7::Exception {"incompatible texture target"};
}
});
}
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>> Obj_BufferMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create buffer");
GLuint id;
glGenBuffers(1, &id);
pro.Return(std::make_shared<Obj<Obj_BufferMeta>>(ctx, id, *this));
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>> Obj_TextureMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create texture");
GLuint id;
glGenTextures(1, &id);
const auto t = gl::ToEnum(target);
glBindTexture(t, id);
glTexParameteri(t, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(t, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(t, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(t, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
const auto ifmt = static_cast<GLint>(gl::ToEnum(format));
const GLenum fmt = gl::IsColor(format)? GL_RED: GL_DEPTH_COMPONENT;
switch (gl::GetDimension(target)) {
case 2:
glTexImage2D(t, 0, ifmt, size[0], size[1], 0,
fmt, GL_UNSIGNED_BYTE, nullptr);
break;
default:
assert(false && "unknown texture target");
break;
}
glBindTexture(t, 0);
assert(0 == glGetError());
pro.Return(std::make_shared<Obj<Obj_TextureMeta>>(ctx, id, *this));
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>> Obj_ShaderMeta::Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::string& src) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>>::Promise pro {ctx};
ctx->env().ExecGL(ctx, [=, *this]() mutable {
ZoneScopedN("create shader");
const auto t = gl::ToEnum(type);
const auto id = glCreateShader(t);
if (id == 0) {
pro.Throw<nf7::Exception>("failed to allocate new shader");
return;
}
static const char* kHeader =
"#version 330\n"
"#extension GL_ARB_shading_language_include: require\n";
{
ZoneScopedN("compile");
const GLchar* str[] = {kHeader, src.c_str()};
glShaderSource(id, 2, str, nullptr);
glCompileShader(id);
assert(0 == glGetError());
}
GLint status;
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (status == GL_TRUE) {
pro.Return(std::make_shared<Obj<Obj_ShaderMeta>>(ctx, id, *this));
} else {
GLint len;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &len);
std::string ret(static_cast<size_t>(len), ' ');
glGetShaderInfoLog(id, len, nullptr, ret.data());
pro.Throw<nf7::Exception>(std::move(ret));
}
});
return pro.future();
}
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>> Obj_ProgramMeta::Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::vector<nf7::File::Id>& shaders) noexcept {
nf7::AggregatePromise apro {ctx};
std::vector<nf7::Future<nf7::Mutex::Resource<std::shared_ptr<gl::Shader>>>> shs;
for (auto shader : shaders) {
shs.emplace_back(ctx->env().GetFileOrThrow(shader).
interfaceOrThrow<nf7::gl::Shader::Factory>().Create());
apro.Add(shs.back());
}
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>>::Promise pro {ctx};
apro.future().Chain(nf7::Env::kGL, ctx, pro, [*this, ctx, shs = std::move(shs)](auto&) {
ZoneScopedN("create program");
// check all shaders
for (auto& sh : shs) { sh.value(); }
// create program
const auto id = glCreateProgram();
if (id == 0) {
throw nf7::Exception {"failed to allocate new program"};
}
// attach shaders
for (auto& sh : shs) {
glAttachShader(id, (*sh.value())->id());
}
{
ZoneScopedN("link");
glLinkProgram(id);
}
// check status
GLint status;
glGetProgramiv(id, GL_LINK_STATUS, &status);
if (status == GL_TRUE) {
return std::make_shared<Obj<Obj_ProgramMeta>>(ctx, id, *this);
} else {
GLint len;
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &len);
std::string ret(static_cast<size_t>(len), ' ');
glGetProgramInfoLog(id, len, nullptr, ret.data());
throw nf7::Exception {std::move(ret)};
}
});
return pro.future();
}
void Obj_ProgramMeta::ApplyState() const noexcept {
if (depth) {
glEnable(GL_DEPTH_TEST);
glDepthRange(depth->near, depth->far);
glDepthFunc(gl::ToEnum(depth->func));
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void Obj_ProgramMeta::RevertState() const noexcept {
glBlendFunc(GL_ONE, GL_ZERO);
glDisable(GL_BLEND);
if (depth) {
glDisable(GL_DEPTH_TEST);
}
}
nf7::Future<std::shared_ptr<Obj<Obj_VertexArrayMeta>>> Obj_VertexArrayMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept
try {
if (index) {
if (index->numtype != gl::NumericType::U8 &&
index->numtype != gl::NumericType::U16 &&
index->numtype != gl::NumericType::U32) {
throw nf7::Exception {"invalid index buffer numtype (only u8/u16/u32 are allowed)"};
}
}
nf7::Future<std::shared_ptr<Obj<Obj_VertexArrayMeta>>>::Promise pro {ctx};
LockAttachments(ctx).Chain(
nf7::Env::kGL, ctx, pro,
[*this, ctx, pro](auto& bufs) mutable {
ZoneScopedN("create va");
// check all buffers
if (index) {
assert(bufs.index);
const auto& m = (***bufs.index).meta();
if (m.target != gl::BufferTarget::ElementArray) {
throw nf7::Exception {"index buffer is not ElementArray"};
}
}
assert(bufs.attrs.size() == attrs.size());
for (size_t i = 0; i < attrs.size(); ++i) {
if ((**bufs.attrs[i]).meta().target != gl::BufferTarget::Array) {
throw nf7::Exception {"buffer is not Array"};
}
}
GLuint id;
glGenVertexArrays(1, &id);
glBindVertexArray(id);
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
const auto& buf = **bufs.attrs[i];
glBindBuffer(GL_ARRAY_BUFFER, buf.id());
glEnableVertexAttribArray(attr.location);
glVertexAttribDivisor(attr.location, attr.divisor);
glVertexAttribPointer(
attr.location,
attr.size,
gl::ToEnum(attr.type),
attr.normalize,
attr.stride,
reinterpret_cast<GLvoid*>(static_cast<GLintptr>(attr.offset)));
}
if (index) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (***bufs.index).id());
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
assert(0 == glGetError());
return std::make_shared<Obj<Obj_VertexArrayMeta>>(ctx, id, *this);
});
return pro.future();
} catch (nf7::Exception&) {
return {std::current_exception()};
}
Obj_VertexArrayMeta::LockedAttachmentsFuture Obj_VertexArrayMeta::LockAttachments(
const std::shared_ptr<nf7::Context>& ctx,
const ValidationHint& vhint) const noexcept
try {
const auto Lock = [&](nf7::File::Id id, gl::BufferTarget target, size_t req) {
auto& factory = ctx->env().
GetFileOrThrow(id).interfaceOrThrow<gl::Buffer::Factory>();
return LockAndValidate(ctx, factory, target, req);
};
nf7::AggregatePromise apro {ctx};
auto ret = std::make_shared<LockedAttachments>();
LockedAttachmentsFuture::Promise pro {ctx};
// lock array buffers
std::unordered_map<nf7::File::Id, gl::Buffer::Factory::Product> attrs_fu_map;
for (size_t i = 0; i < attrs.size(); ++i) {
const auto& attr = attrs[i];
const size_t required =
// when non-instanced and no-index-buffer drawing
attr.divisor == 0 && vhint.vertices > 0 && !index?
static_cast<size_t>(attr.size) * vhint.vertices * gl::GetByteSize(attr.type):
// when instanced drawing
attr.divisor > 0 && vhint.instances > 0?
static_cast<size_t>(attr.stride) * (vhint.instances-1) + attr.offset:
size_t {0};
if (attrs_fu_map.end() == attrs_fu_map.find(attr.buffer)) {
auto [itr, add] = attrs_fu_map.emplace(
attr.buffer, Lock(attr.buffer, gl::BufferTarget::Array, required));
(void) add;
apro.Add(itr->second);
}
}
// serialize attrs_fu_map
std::vector<gl::Buffer::Factory::Product> attrs_fu;
for (const auto& attr : attrs) {
auto itr = attrs_fu_map.find(attr.buffer);
assert(itr != attrs_fu_map.end());
attrs_fu.push_back(itr->second);
}
// lock index buffers (it must be the last element in `fus`)
if (index) {
const auto required = gl::GetByteSize(index->numtype) * vhint.vertices;
apro.Add(Lock(index->buffer, gl::BufferTarget::ElementArray, required).
Chain(pro, [ret](auto& buf) { ret->index = buf; }));
}
// return ret
apro.future().Chain(pro, [ret, attrs_fu = std::move(attrs_fu)](auto&) {
ret->attrs.reserve(attrs_fu.size());
for (auto& fu : attrs_fu) {
ret->attrs.push_back(fu.value());
}
return std::move(*ret);
});
return pro.future();
} catch (nf7::Exception&) {
return { std::current_exception() };
}
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>> Obj_FramebufferMeta::Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept {
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>>::Promise pro {ctx};
LockAttachments(ctx).
Chain(nf7::Env::kGL, ctx, pro, [ctx, *this](auto& k) mutable {
ZoneScopedN("create fb");
GLuint id;
glGenFramebuffers(1, &id);
glBindFramebuffer(GL_FRAMEBUFFER, id);
for (size_t i = 0; i < colors.size(); ++i) {
if (const auto& tex = k.colors[i]) {
glFramebufferTexture(GL_FRAMEBUFFER,
static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + i),
(***tex).id(),
0 /* = level */);
}
}
if (k.depth) {
glFramebufferTexture(GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
(***k.depth).id(),
0 /* = level */);
}
if (k.stencil) {
glFramebufferTexture(GL_FRAMEBUFFER,
GL_STENCIL_ATTACHMENT,
(***k.stencil).id(),
0 /* = level */);
}
const auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
const auto ret = std::make_shared<Obj<Obj_FramebufferMeta>>(ctx, id, *this);
if (0 != glGetError()) {
throw nf7::Exception {"failed to setup framebuffer"};
}
if (status != GL_FRAMEBUFFER_COMPLETE) {
throw nf7::Exception {"invalid framebuffer status"};
}
return ret;
});
return pro.future();
}
Obj_FramebufferMeta::LockedAttachmentsFuture Obj_FramebufferMeta::LockAttachments(
const std::shared_ptr<nf7::Context>& ctx) const noexcept
try {
auto ret = std::make_shared<LockedAttachments>();
// file duplication check for preventing deadlock by double lock
std::unordered_set<nf7::File::Id> locked;
for (const auto& col : colors) {
if (col && col->tex && !locked.insert(col->tex).second) {
throw nf7::Exception {"attached color texture is duplicated"};
}
}
if (depth && depth->tex && !locked.insert(depth->tex).second) {
throw nf7::Exception {"attached depth texture is duplicated"};
}
if (stencil && stencil->tex && !locked.insert(stencil->tex).second) {
throw nf7::Exception {"attached stencil texture is duplicated"};
}
nf7::AggregatePromise apro {ctx};
LockedAttachmentsFuture::Promise pro {ctx};
const auto Lock = [&](nf7::File::Id id) {
auto& factory = ctx->env().
GetFileOrThrow(id).
interfaceOrThrow<gl::Texture::Factory>();
return LockAndValidate(ctx, factory, gl::TextureTarget::Tex2D);
};
for (size_t i = 0; i < colors.size(); ++i) {
const auto& color = colors[i];
if (color && color->tex) {
apro.Add(Lock(color->tex).Chain(pro, [i, ret](auto& res) {
ret->colors[i] = res;
}));
}
}
if (depth && depth->tex) {
apro.Add(Lock(depth->tex).Chain(pro, [ret](auto& res) {
ret->depth = res;
}));
}
if (stencil && stencil->tex) {
apro.Add(Lock(stencil->tex).Chain(pro, [ret](auto& res) {
ret->stencil = res;
}));
}
apro.future().Chain(pro, [ret](auto&) { return std::move(*ret); });
return pro.future();
} catch (nf7::Exception&) {
return { std::current_exception() };
}
} // namespace nf7::gl

View File

@ -1,236 +0,0 @@
#pragma once
#include <array>
#include <cassert>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <GL/glew.h>
#include "nf7.hh"
#include "common/factory.hh"
#include "common/future.hh"
#include "common/gl_enum.hh"
#include "common/mutex.hh"
namespace nf7::gl {
template <typename T>
class Obj final {
public:
using Meta = T;
using Param = typename Meta::Param;
using Factory = nf7::AsyncFactory<nf7::Mutex::Resource<std::shared_ptr<Obj<T>>>>;
Obj(const std::shared_ptr<nf7::Context>& ctx, GLuint id, const Meta& meta) noexcept :
ctx_(ctx), id_(id), meta_(meta) {
}
~Obj() noexcept {
ctx_->env().ExecGL(ctx_, [id = id_]() { T::Delete(id); });
}
Obj(const Obj&) = delete;
Obj(Obj&&) = delete;
Obj& operator=(const Obj&) = delete;
Obj& operator=(Obj&&) = delete;
GLuint id() const noexcept { return id_; }
const Meta& meta() const noexcept { return meta_; }
Param& param() noexcept { return param_; }
const Param& param() const noexcept { return param_; }
private:
std::shared_ptr<nf7::Context> ctx_;
const GLuint id_;
const Meta meta_;
Param param_;
};
struct Obj_BufferMeta final {
public:
struct Param { size_t size = 0; };
static void Delete(GLuint id) noexcept {
glDeleteBuffers(1, &id);
}
nf7::Future<std::shared_ptr<Obj<Obj_BufferMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept;
gl::BufferTarget target;
};
using Buffer = Obj<Obj_BufferMeta>;
struct Obj_TextureMeta final {
public:
struct Param { };
static void Delete(GLuint id) noexcept {
glDeleteTextures(1, &id);
}
nf7::Future<std::shared_ptr<Obj<Obj_TextureMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept;
gl::TextureTarget target;
gl::InternalFormat format;
std::array<GLsizei, 3> size;
};
using Texture = Obj<Obj_TextureMeta>;
struct Obj_ShaderMeta final {
public:
struct Param { };
static void Delete(GLuint id) noexcept {
glDeleteShader(id);
}
nf7::Future<std::shared_ptr<Obj<Obj_ShaderMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::string& src) const noexcept;
gl::ShaderType type;
};
using Shader = Obj<Obj_ShaderMeta>;
struct Obj_ProgramMeta final {
public:
struct Param { };
struct Depth {
float near = 0, far = 1;
gl::TestFunc func = gl::TestFunc::Less;
void serialize(auto& ar) { ar(near, far, func); }
};
static void Delete(GLuint id) noexcept {
glDeleteProgram(id);
}
nf7::Future<std::shared_ptr<Obj<Obj_ProgramMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx,
const std::vector<nf7::File::Id>& shaders) noexcept;
void ApplyState() const noexcept;
void RevertState() const noexcept;
std::optional<Depth> depth;
};
using Program = Obj<Obj_ProgramMeta>;
struct Obj_VertexArrayMeta final {
public:
struct Param { };
struct LockedAttachments {
std::optional<nf7::Mutex::Resource<std::shared_ptr<gl::Buffer>>> index;
std::vector<nf7::Mutex::Resource<std::shared_ptr<gl::Buffer>>> attrs;
};
using LockedAttachmentsFuture = nf7::Future<LockedAttachments>;
struct Index {
nf7::File::Id buffer;
gl::NumericType numtype;
};
struct Attr {
nf7::File::Id buffer;
GLuint location;
GLint size;
gl::NumericType type;
bool normalize;
GLsizei stride;
uint64_t offset;
GLuint divisor;
};
struct ValidationHint {
size_t vertices = 0;
size_t instances = 0;
};
static void Delete(GLuint id) noexcept {
glDeleteVertexArrays(1, &id);
}
nf7::Future<std::shared_ptr<Obj<Obj_VertexArrayMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept;
// it's guaranteed that the last element of the returned vector is an index buffer if index != std::nullopt
LockedAttachmentsFuture LockAttachments(
const std::shared_ptr<nf7::Context>& ctx,
const ValidationHint& vhint = {0, 0}) const noexcept;
std::optional<Index> index;
std::vector<Attr> attrs;
};
using VertexArray = Obj<Obj_VertexArrayMeta>;
struct Obj_FramebufferMeta final {
public:
static constexpr size_t kColorSlotCount = 8;
struct Param { };
struct Attachment {
nf7::File::Id tex;
};
struct LockedAttachments {
private:
using TexRes = nf7::Mutex::Resource<std::shared_ptr<gl::Texture>>;
public:
std::array<std::optional<TexRes>, kColorSlotCount> colors;
std::optional<TexRes> depth;
std::optional<TexRes> stencil;
};
using LockedAttachmentsFuture = nf7::Future<LockedAttachments>;
static void Delete(GLuint id) noexcept {
glDeleteFramebuffers(1, &id);
}
nf7::Future<std::shared_ptr<Obj<Obj_FramebufferMeta>>> Create(
const std::shared_ptr<nf7::Context>& ctx) const noexcept;
LockedAttachmentsFuture LockAttachments(
const std::shared_ptr<nf7::Context>& ctx) const noexcept;
std::array<std::optional<Attachment>, kColorSlotCount> colors;
std::optional<Attachment> depth;
std::optional<Attachment> stencil;
};
using Framebuffer = Obj<Obj_FramebufferMeta>;
// acquires locks of the object and all of its attachments
template <typename T, typename... Args>
auto LockRecursively(typename T::Factory& factory,
const std::shared_ptr<nf7::Context>& ctx,
Args&&... args) noexcept {
typename nf7::Future<std::pair<nf7::Mutex::Resource<std::shared_ptr<T>>,
typename T::Meta::LockedAttachments>>::Promise pro {ctx};
factory.Create().Chain(pro, [=, ...args = std::forward<Args>(args)](auto& obj) mutable {
(**obj).meta().LockAttachments(ctx, std::forward<Args>(args)...).
Chain(pro, [obj](auto& att) {
return std::make_pair(obj, att);
});
});
return pro.future();
}
} // namespace nf7::gl

View File

@ -1,114 +0,0 @@
#pragma once
#include <filesystem>
#include <fstream>
#include <memory>
#include <sstream>
#include <string_view>
#include <vector>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7::gl {
class ShaderPreproc final : public nf7::Context,
public std::enable_shared_from_this<ShaderPreproc> {
public:
ShaderPreproc() = delete;
ShaderPreproc(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<std::ostream>& ost,
const std::shared_ptr<std::istream>& ist,
std::filesystem::path&& path) noexcept :
nf7::Context(ctx->env(), ctx->initiator(), ctx),
pro_(ctx), ost_(ost), ist_(ist), path_(std::move(path)) {
}
ShaderPreproc(const ShaderPreproc&) = delete;
ShaderPreproc(ShaderPreproc&&) = delete;
ShaderPreproc& operator=(const ShaderPreproc&) = delete;
ShaderPreproc& operator=(ShaderPreproc&&) = delete;
void ExecProcess() noexcept {
env().ExecAsync(shared_from_this(), [this]() { Process(); });
}
nf7::Future<std::monostate> future() noexcept {
return pro_.future();
}
const std::vector<std::filesystem::path>& nfiles() const noexcept {
static const std::vector<std::filesystem::path> kEmpty = {};
return nfiles_? *nfiles_: kEmpty;
}
private:
nf7::Future<std::monostate>::Promise pro_;
std::shared_ptr<std::ostream> ost_;
std::shared_ptr<std::istream> ist_;
std::filesystem::path path_;
size_t lnum_ = 1;
std::shared_ptr<std::vector<std::filesystem::path>> nfiles_;
void Process() noexcept
try {
*ost_ << "#line " << lnum_ << " \"" << path_.string() << "\"\n";
std::string line;
while (std::getline(*ist_, line)) {
++lnum_;
if (line.starts_with('#')) {
std::string_view tok {line.begin() + 1, line.end()};
while (!tok.empty() && !std::isalpha(tok.front())) {
tok.remove_prefix(1);
}
if (tok.starts_with("include ")) {
tok.remove_prefix(sizeof("include")-1);
auto begin = std::find(tok.begin(), tok.end(), '"');
auto end = std::find(begin+1, tok.end(), '"');
if (begin == end || end == tok.end()) {
throw nf7::Exception {"invalid include syntax: "+line};
}
if (depth() >= 100) {
throw nf7::Exception {
"recursion detected in include directives ("+path_.string()+")"};
}
const std::string name {begin+1, end};
const auto path = path_.parent_path() / name;
if (nfiles_ == nullptr) {
nfiles_ = std::make_shared<std::vector<std::filesystem::path>>();
}
nfiles_->push_back(path);
auto self = shared_from_this();
auto f = std::make_shared<std::ifstream>(path, std::ios::binary);
if (!*f) {
throw nf7::Exception {"missing include file: "+path.string()};
}
auto sub = std::make_shared<ShaderPreproc>(self, ost_, f, path.string());
sub->nfiles_ = nfiles_;
sub->Process();
sub->future().Chain(nf7::Env::kAsync, self, pro_,
[=, this](auto&) mutable { Process(); });
return;
}
}
*ost_ << line << "\n";
}
pro_.Return({});
} catch (...) {
pro_.Throw<nf7::Exception>("failed to preprocess GLSL");
}
};
} // namespace nf7::gl

View File

@ -1,246 +0,0 @@
#include "common/gui.hh"
#include <optional>
#include <string>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include "nf7.hh"
#include "common/config.hh"
#include "common/dir_item.hh"
#include "common/gui_dnd.hh"
namespace nf7::gui {
void FileMenuItems(nf7::File& f) noexcept {
auto ditem = f.interface<nf7::DirItem>();
auto config = f.interface<nf7::Config>();
if (ImGui::MenuItem("request focus")) {
f.env().Handle({.id = f.id(), .type = nf7::File::Event::kReqFocus});
}
if (ImGui::MenuItem("copy path")) {
ImGui::SetClipboardText(f.abspath().Stringify().c_str());
}
if (ditem && (ditem->flags() & nf7::DirItem::kMenu)) {
ImGui::Separator();
ditem->UpdateMenu();
}
if (config) {
ImGui::Separator();
if (ImGui::BeginMenu("config")) {
static nf7::gui::ConfigEditor ed;
ed(*config);
ImGui::EndMenu();
}
}
}
void FileTooltip(nf7::File& f) noexcept {
auto ditem = f.interface<nf7::DirItem>();
ImGui::TextUnformatted(f.type().name().c_str());
ImGui::SameLine();
ImGui::TextDisabled(f.abspath().Stringify().c_str());
if (ditem && (ditem->flags() & nf7::DirItem::kTooltip)) {
ImGui::Indent();
ditem->UpdateTooltip();
ImGui::Unindent();
}
}
bool PathButton(const char* id, nf7::File::Path& p, nf7::File& base) noexcept {
bool ret = false;
const auto pstr = p.Stringify();
const auto w = ImGui::CalcItemWidth();
ImGui::PushID(id);
// widget body
{
nf7::File* file = nullptr;
try {
file = &base.ResolveOrThrow(p);
} catch (nf7::Exception&) {
}
const auto display = pstr.empty()? "(empty)": pstr;
if (ImGui::Button(display.c_str(), {w, 0})) {
ImGui::OpenPopup("editor");
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (file) {
FileTooltip(*file);
} else {
ImGui::TextDisabled("(file missing)");
}
ImGui::EndTooltip();
}
if (ImGui::BeginPopupContextItem()) {
if (file) {
nf7::gui::FileMenuItems(*file);
} else {
ImGui::TextDisabled("(file missing)");
}
ImGui::EndPopup();
}
if (ImGui::BeginDragDropTarget()) {
if (auto dp = nf7::gui::dnd::Accept<nf7::File::Path>(nf7::gui::dnd::kFilePath)) {
p = std::move(*dp);
ret = true;
}
ImGui::EndDragDropTarget();
}
if (id[0] != '#') {
ImGui::SameLine();
ImGui::TextUnformatted(id);
}
}
// editor popup
if (ImGui::BeginPopup("editor")) {
static std::string editing_str;
if (ImGui::IsWindowAppearing()) {
editing_str = pstr;
}
bool submit = false;
if (ImGui::InputText("path", &editing_str, ImGuiInputTextFlags_EnterReturnsTrue)) {
submit = true;
}
std::optional<nf7::File::Path> newpath;
try {
newpath = nf7::File::Path::Parse(editing_str);
} catch (nf7::Exception& e) {
ImGui::Text("invalid path: %s", e.msg().c_str());
}
ImGui::BeginDisabled(!newpath);
if (ImGui::Button("ok")) {
submit = true;
}
ImGui::EndDisabled();
if (newpath && submit) {
ImGui::CloseCurrentPopup();
p = std::move(*newpath);
ret = true;
}
ImGui::EndPopup();
}
ImGui::PopID();
return ret;
}
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());
}
}
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);
}
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();
}
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();
}
void ConfigEditor::operator()(nf7::Config& config) noexcept {
ImGui::PushID(this);
if (ImGui::IsWindowAppearing()) {
text_ = config.Stringify();
msg_ = "";
mod_ = false;
}
mod_ |= ImGui::InputTextMultiline("##config", &text_);
ImGui::BeginDisabled(!mod_);
if (ImGui::Button("apply")) {
try {
config.Parse(text_);
msg_ = "";
mod_ = false;
} catch (nf7::Exception& e) {
msg_ = e.msg();
} catch (std::exception& e) {
msg_ = e.what();
}
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("restore")) {
text_ = config.Stringify();
msg_ = "";
mod_ = false;
}
if (msg_.size()) {
ImGui::Bullet();
ImGui::TextUnformatted(msg_.c_str());
}
ImGui::PopID();
}
} // namespace nf7::gui

View File

@ -1,59 +0,0 @@
#pragma once
#include <cinttypes>
#include <cstdint>
#include <cstring>
#include <string>
#include "nf7.hh"
#include "common/config.hh"
namespace nf7::gui {
// widgets
void FileMenuItems(nf7::File& f) noexcept;
void FileTooltip(nf7::File& f) noexcept;
bool PathButton(const char* id, nf7::File::Path&, nf7::File&) noexcept;
void ContextStack(const nf7::Context&) noexcept;
void NodeSocket() noexcept;
void NodeInputSockets(std::span<const std::string>) noexcept;
void NodeOutputSockets(std::span<const std::string>) noexcept;
struct ConfigEditor {
public:
void operator()(nf7::Config&) noexcept;
private:
std::string text_;
std::string msg_;
bool mod_;
};
// stringify utility
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? ;(";
}
}
} // 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,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,47 +0,0 @@
#include "common/gui_window.hh"
#include <imgui.h>
#include <imgui_internal.h>
namespace nf7::gui {
bool Window::MenuItem() noexcept {
return ImGui::MenuItem(title_.c_str(), nullptr, &shown_);
}
void Window::Handle(const nf7::File::Event& e) noexcept {
switch (e.type) {
case nf7::File::Event::kReqFocus:
SetFocus();
return;
default:
return;
}
}
void Window::Update() noexcept {
const auto idstr = id();
auto win = ImGui::FindWindowByName(idstr.c_str());
if (std::exchange(set_focus_, false)) {
shown_ = true;
ImGui::SetNextWindowFocus();
// activate parent windows recursively
auto node = win && win->DockNode? win->DockNode->HostWindow: nullptr;
while (node) {
ImGui::SetWindowFocus(node->Name);
node = node->ParentWindow;
}
}
if (!shown_) return;
onConfig();
if (ImGui::Begin(idstr.c_str(), &shown_)) {
onUpdate();
}
ImGui::End();
}
} // namespace nf7::gui

View File

@ -1,68 +0,0 @@
#pragma once
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#include <yas/serialize.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
namespace nf7::gui {
class Window : public nf7::FileBase::Feature {
public:
static std::string ConcatId(nf7::File& f, const std::string& name) noexcept {
return f.abspath().Stringify() + " | " + std::string {name};
}
Window() = delete;
Window(nf7::FileBase& owner, std::string_view title) noexcept :
nf7::FileBase::Feature(owner), owner_(&owner), title_(title), shown_(false) {
}
Window(const Window&) = delete;
Window(Window&&) = delete;
Window& operator=(const Window&) = delete;
Window& operator=(Window&&) = delete;
void serialize(auto& ar) {
ar(shown_);
}
void Show() noexcept {
shown_ = true;
}
void SetFocus() noexcept {
shown_ = true;
set_focus_ = true;
}
bool MenuItem() noexcept;
std::string id() const noexcept { return ConcatId(*owner_, title_); }
bool shown() const noexcept { return shown_; }
std::function<void()> onConfig = [](){};
std::function<void()> onUpdate;
private:
File* const owner_;
std::string title_;
bool need_end_ = false;
bool set_focus_ = false;
// persistent params
bool shown_;
void Handle(const nf7::File::Event&) noexcept override;
void Update() noexcept override;
};
} // 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:
struct Data final {
std::atomic<T*> ptr;
};
class 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_;
};
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;
Ref ref() const noexcept { return *this; }
private:
T* const ptr_;
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::FileBase& f, nf7::File::Path&& p = {"_logger"}) noexcept :
nf7::FileBase::Feature(f), 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,525 +0,0 @@
#include "common/luajit.hh"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cinttypes>
#include <cctype>
#include <string>
#include <string_view>
#include <variant>
#include <lua.hpp>
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/luajit_thread.hh"
#include "common/luajit_std.hh"
namespace nf7::luajit {
// 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 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;
auto operator()(const Value::Pulse&) noexcept { lua_pushnil(L); }
auto operator()(const Value::Boolean& v) noexcept { lua_pushboolean(L, v); }
auto operator()(const Value::Integer& v) noexcept { lua_pushinteger(L, v); }
auto operator()(const Value::Scalar& v) noexcept { lua_pushnumber(L, v); }
auto operator()(const Value::String& v) noexcept { lua_pushstring(L, v.c_str()); }
auto operator()(const Value::ConstVector& v) noexcept { PushVector(L, v); }
auto operator()(const Value::DataPtr&) noexcept { lua_pushnil(L); }
auto operator()(const Value::ConstTuple& v) noexcept {
const auto& tup = *v;
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());
}
}
}
};
std::visit(Visitor {.L = L}, v.value());
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);
}
std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
// get absolute position on stack because recursion call may occur
if (idx < 0) {
idx = lua_gettop(L)+idx+1;
}
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_type(L, -2) == LUA_TSTRING) {
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;
}
void PushVector(lua_State* L, const nf7::Value::ConstVector& v) noexcept {
static const char* kTypeName = "nf7::Value::ConstVector";
using T = nf7::Value::ConstVector;
assert(v != nullptr);
new (lua_newuserdata(L, sizeof(v))) nf7::Value::ConstVector(v);
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<T>(L, 1, kTypeName);
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<T>(L, 1, kTypeName);
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<T>(L, 1, kTypeName);
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<T>(L, 1, kTypeName).~shared_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
constexpr const char* kTypeName = "nf7::Value::MutableVector";
using T = std::vector<uint8_t>;
new (lua_newuserdata(L, sizeof(v))) T(std::move(v));
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
lua_pushcfunction(L, [](auto L) {
auto& v = CheckRef<T>(L, 1, kTypeName);
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<T>(L, 1, kTypeName);
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<T>(L, 1, kTypeName);
const auto dst_off = luaL_checkinteger(L, 2);
const 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<T>(L, 1, kTypeName).~vector();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushNodeRootLambda(
lua_State* L, const std::shared_ptr<nf7::NodeRootLambda>& la) noexcept {
assert(la);
using T = std::shared_ptr<nf7::NodeRootLambda>;
new (lua_newuserdata(L, sizeof(T))) T {la};
if (luaL_newmetatable(L, "nf7::NodeRootLambda")) {
lua_createtable(L, 0, 0);
{
// la:send(nf7, key, value)
lua_pushcfunction(L, [](auto L) {
auto la = CheckNodeRootLambda(L, 1);
la->ExecSend(luaL_checkstring(L, 2), luajit::CheckValue(L, 3));
return 0;
});
lua_setfield(L, -2, "send");
// la:recv(nf7, {name1, name2, ...})
lua_pushcfunction(L, [](auto L) {
auto la = CheckNodeRootLambda(L, 1);
auto th = luajit::Thread::GetPtr(L, 2);
std::vector<std::string> names;
ToStringList(L, 3, names);
if (names.size() == 0) {
return 0;
}
auto fu = la->Select(
std::unordered_set<std::string>(names.begin(), names.end()));
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&) {
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);
});
return th->Yield(L, la);
}
});
lua_setfield(L, -2, "recv");
}
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckNodeRootLambda(L, 1).~shared_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushGlobalTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::GlobalTable")) {
PushStdTable(L);
lua_setfield(L, -2, "std");
}
}
void PushImmEnv(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::ImmEnv")) {
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 PushImmTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::ImmTable")) {
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "table is immutable"); });
lua_setfield(L, -2, "__newindex");
}
}
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,148 +0,0 @@
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
#include <lua.hpp>
#include "common/node_root_lambda.hh"
#include "common/value.hh"
namespace nf7::luajit {
// ---- utility
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, typename... Args>
inline T& NewUserData(lua_State* L, Args&&... args) noexcept {
return *(new (lua_newuserdata(L, sizeof(T))) T(std::forward<Args>(args)...));
}
// ---- reference conversion
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 T& CheckRef(lua_State* L, int idx, const char* type) {
return *reinterpret_cast<T*>(luaL_checkudata(L, idx, type));
}
// ---- Value conversion
void PushValue(lua_State*, const nf7::Value&) noexcept;
std::optional<nf7::Value> ToValue(lua_State*, int) noexcept;
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);
}
void PushVector(lua_State*, const nf7::Value::ConstVector&) noexcept;
inline 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;
}
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
inline 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);
}
void PushNodeRootLambda(
lua_State*, const std::shared_ptr<nf7::NodeRootLambda>&) noexcept;
inline const std::shared_ptr<nf7::NodeRootLambda>& CheckNodeRootLambda(lua_State* L, int idx) {
return CheckRef<std::shared_ptr<nf7::NodeRootLambda>>(L, idx, "nf7::NodeRootLambda");
}
inline void ToStringList(lua_State* L, int idx, std::vector<std::string>& v) noexcept {
v.clear();
if (!lua_istable(L, idx)) {
if (auto str = lua_tostring(L, idx)) {
v.emplace_back(str);
}
return;
}
const size_t n = lua_objlen(L, idx);
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);
}
}
// ---- overloaded Push function for template
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::NodeRootLambda>& la) noexcept {
luajit::PushNodeRootLambda(L, la);
}
// pushes all args and returns a number of them
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)...);
}
// ---- global table
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
void PushImmTable(lua_State*) noexcept;
} // namespace nf7

View File

@ -1,107 +0,0 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <lua.hpp>
#include "nf7.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
namespace nf7::luajit {
class NFileImporter :
public nf7::luajit::Thread::Importer,
public std::enable_shared_from_this<NFileImporter> {
public:
NFileImporter(const std::filesystem::path& base) noexcept : base_(base) {
}
nf7::Future<std::shared_ptr<luajit::Ref>> Import(
const std::shared_ptr<luajit::Thread>& th, std::string_view name) noexcept {
auto self = shared_from_this();
const auto path = base_ / std::string {name};
auto ljq = th->ljq();
auto ctx = std::make_shared<
nf7::GenericContext>(th->env(), th->initiator(), "imported LuaJIT script", th);
nf7::Future<std::shared_ptr<luajit::Ref>>::Promise pro {ctx};
// create new thread
auto handler = luajit::Thread::CreatePromiseHandler<std::shared_ptr<luajit::Ref>>(
pro, [self, this, path, ljq, ctx](auto L) {
if (lua_gettop(L) <= 1) {
AddImport(path);
return std::make_shared<nf7::luajit::Ref>(ctx, ljq, L);
} else {
throw nf7::Exception {"imported script can return 1 or less results"};
}
});
auto th_sub = std::make_shared<
nf7::luajit::Thread>(ctx, ljq, std::move(handler));
th_sub->Install(*th);
// install new importer for sub thread
auto dir = path;
dir.remove_filename();
th_sub->Install(std::make_shared<NFileImporter>(dir));
// start the thread
ljq->Push(ctx, [pro, path, th_sub](auto L) mutable {
L = th_sub->Init(L);
if (0 == luaL_loadfile(L, path.string().c_str())) {
th_sub->Resume(L, 0);
} else {
pro.Throw<nf7::Exception>(std::string {"import failed: "}+lua_tostring(L, -1));
}
});
return pro.future();
}
void ClearImports() noexcept {
std::unique_lock<std::mutex> _ {mtx_};
imports_.clear();
}
std::filesystem::file_time_type GetLatestMod() const noexcept {
std::unique_lock<std::mutex> _ {mtx_};
auto ret = std::filesystem::file_time_type::min();
for (const auto& p : imports_) {
try {
ret = std::max(ret, std::filesystem::last_write_time(p));
} catch (std::filesystem::filesystem_error&) {
}
}
return ret;
}
private:
const std::filesystem::path base_;
mutable std::mutex mtx_;
std::vector<std::string> imports_;
void AddImport(const std::filesystem::path& p) noexcept {
auto str = p.string();
std::unique_lock<std::mutex> _ {mtx_};
if (imports_.end() == std::find(imports_.begin(), imports_.end(), str)) {
imports_.emplace_back(std::move(str));
}
}
};
} // namespace nf7::luajit

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,51 +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_;
};
inline void Push(lua_State* L, const std::shared_ptr<Ref>& ref) noexcept {
ref->PushSelf(L);
}
} // namespace nf7::luajit

View File

@ -1,105 +0,0 @@
#include <lua.hpp>
#include "common/luajit.hh"
namespace nf7::luajit {
inline void PushStdTable(lua_State* L) noexcept {
luaL_openlibs(L);
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// ---- 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");
// ---- lua std libs ----
const auto Copy =
[L](const char* name, const char* expr, bool imm) {
luaL_loadstring(L, expr);
lua_call(L, 0, 1);
if (imm) {
PushImmTable(L);
lua_setmetatable(L, -2);
}
lua_setfield(L, -2, name);
};
Copy("assert", "return assert", false);
Copy("error", "return error", false);
Copy("ipairs", "return ipairs", false);
Copy("loadstring", "return loadstring", false);
Copy("next", "return next", false);
Copy("pairs", "return pairs", false);
Copy("pcall", "return pcall", false);
Copy("rawequal", "return rawequal", false);
Copy("rawget", "return rawget", false);
Copy("select", "return select", false);
Copy("setfenv", "return setfenv", false);
Copy("setmetatable", "return setmetatable", false);
Copy("tonumber", "return tonumber", false);
Copy("tostring", "return tostring", false);
Copy("type", "return type", false);
Copy("unpack", "return unpack", false);
Copy("_VERSION", "return _VERSION", false);
Copy("xpcall", "return xpcall", false);
Copy("bit", "return require(\"bit\")", true);
Copy("coroutine", "return coroutine", true);
Copy("math", "return math", true);
Copy("string", "return string", true);
Copy("table", "return table", true);
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
} // namespace nf7::luajit

View File

@ -1,273 +0,0 @@
#include "common/luajit_thread.hh"
#include <chrono>
#include <sstream>
#include <tuple>
#include <unordered_set>
#include <tracy/Tracy.hpp>
#include "common/node.hh"
#include "common/node_root_lambda.hh"
namespace nf7::luajit {
constexpr size_t kInstructionLimit = 100000;
// 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(shared_from_this(), 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);
// set global table
PushGlobalTable(L);
NewUserData<std::weak_ptr<Thread>>(L, weak_from_this());
PushMeta(L);
lua_setmetatable(L, -2);
lua_setfield(L, -2, "nf7");
lua_pop(L, 1);
state_ = kRunning;
active_ = true;
yield_ctx_.reset();
k.unlock();
int ret;
{
ZoneScopedN("lua_resume");
ret = lua_resume(L, narg);
}
k.lock();
active_ = false;
if (state_ == kAborted) return;
switch (ret) {
case 0:
th_ref_ = std::nullopt;
state_ = kFinished;
break;
case LUA_YIELD:
state_ = kPaused;
break;
default:
th_ref_ = std::nullopt;
state_ = kAborted;
}
if (!std::exchange(skip_handler_, false)) {
k.unlock();
handler_(*this, L);
}
}
void Thread::Abort() noexcept {
std::unique_lock<std::mutex> k(mtx_);
state_ = kAborted;
th_ref_ = std::nullopt;
auto wctx = std::move(yield_ctx_);
yield_ctx_.reset();
k.unlock();
if (auto ctx = wctx.lock()) {
if (ctx.get() != this) {
ctx->Abort();
}
}
}
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)) {
lua_pushcfunction(L, [](auto L) {
CheckRef<std::weak_ptr<Thread>>(L, 1, Thread::kTypeName).~weak_ptr();
return 0;
});
lua_setfield(L, -2, "__gc");
lua_createtable(L, 0, 0);
{
// nf7:import(npath)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
auto im = th->importer();
if (!im) {
return luaL_error(L, "import is not available in the current thread");
}
if (const auto name = lua_tostring(L, 2)) {
auto fu = im->Import(th, name);
fu.ThenIf([L, th](auto& obj) {
th->ExecResume(L, obj);
}).template Catch<nf7::Exception>([L, th](auto&) {
if (auto log = th->logger()) {
log->Warn("import failed, returning nil");
}
th->ExecResume(L);
});
return th->Yield(L);
} else {
return luaL_error(L, "path should be a string");
}
});
lua_setfield(L, -2, "import");
// nf7:resolve(path)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
auto base = th->initiator();
std::string path = luaL_checkstring(L, 2);
th->env().ExecSub(th, [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);
}
});
return th->Yield(L);
});
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, 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, [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::NodeRootLambda::Create(
th, f.template interfaceOrThrow<nf7::Node>()));
} else {
throw nf7::Exception {"unknown interface: "+iface};
}
} catch (nf7::Exception& e) {
th->ExecResume(L, nullptr, e.msg());
}
});
return th->Yield(L);
});
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, [th, L](auto) { th->ExecResume(L); }, time);
return th->Yield(L);
});
lua_setfield(L, -2, "sleep");
// 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,199 +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/context_owner.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 nf7::Context,
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 Importer;
class Exception final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
// Creates a handler that finalizes a promise.
template <typename T>
static inline Handler CreatePromiseHandler(
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&&) noexcept;
// Creates a handler that emits 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 = CheckRef<std::weak_ptr<Thread>>(L, idx, kTypeName).lock();
if (th) {
th->EnsureActive(L);
return th;
} else {
luaL_error(L, "thread expired");
return nullptr;
}
}
Thread() = delete;
Thread(const std::shared_ptr<nf7::Context>& parent,
const std::shared_ptr<nf7::luajit::Queue>& ljq,
Handler&& handler) noexcept :
nf7::Context(parent->env(), parent->initiator(), parent),
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;
}
void Install(const std::shared_ptr<Importer>& importer) noexcept {
assert(state_ == kInitial);
importer_ = importer;
}
void Install(const Thread& th) noexcept {
assert(state_ == kInitial);
logger_ = th.logger_;
importer_ = th.importer_;
}
// 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 this yielding
int Yield(lua_State* L, const std::shared_ptr<nf7::Context>& ctx = nullptr) {
yield_ctx_ = ctx;
skip_handler_ = true;
return lua_yield(L, 0);
}
// 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 override;
// 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(self, [this, L, args...](auto) mutable {
Resume(L, luajit::PushAll(L, std::forward<Args>(args)...));
});
}
std::string GetDescription() const noexcept override {
return "LuaJIT thread";
}
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
State state() const noexcept { return state_; }
private:
// initialized by constructor
std::mutex mtx_;
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_;
std::shared_ptr<Importer> importer_;
// mutable params
bool active_ = false; // true while executing lua_resume
bool skip_handler_ = false;
std::weak_ptr<nf7::Context> yield_ctx_;
};
class Thread::Importer {
public:
Importer() = default;
virtual ~Importer() = default;
Importer(const Importer&) = delete;
Importer(Importer&&) = delete;
Importer& operator=(const Importer&) = delete;
Importer& operator=(Importer&&) = delete;
// be called on luajit thread
virtual nf7::Future<std::shared_ptr<luajit::Ref>> Import(
const std::shared_ptr<luajit::Thread>&, std::string_view) noexcept = 0;
};
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,81 +0,0 @@
#pragma once
#include <algorithm>
#include <memory>
#include <vector>
#include "nf7.hh"
#include "common/history.hh"
namespace nf7 {
class Memento : public File::Interface {
public:
class Tag;
class RestoreCommand;
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::RestoreCommand final : public nf7::History::Command {
public:
RestoreCommand() = delete;
RestoreCommand(Memento& mem,
const std::shared_ptr<Tag>& prev,
const std::shared_ptr<Tag>& next) noexcept :
mem_(mem), prev_(prev), next_(next) {
}
RestoreCommand(const RestoreCommand&) = delete;
RestoreCommand(RestoreCommand&&) = delete;
RestoreCommand& operator=(const RestoreCommand&) = delete;
RestoreCommand& operator=(RestoreCommand&&) = delete;
void Apply() override { Exec(); }
void Revert() override { Exec(); }
private:
Memento& mem_;
std::shared_ptr<Tag> prev_;
std::shared_ptr<Tag> next_;
void Exec() noexcept {
mem_.Restore(next_);
std::swap(prev_, next_);
}
};
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,169 +0,0 @@
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <utility>
#include "common/life.hh"
#include "common/future.hh"
namespace nf7 {
// nf7::Mutex is not thread-safe except Mutex::Lock's destructor.
class Mutex final {
public:
class Sync;
class Lock;
template <typename T> class Resource;
Mutex() noexcept : life_(*this) {
}
// It's guaranteed that the promise is finalized in a sub task or is done immediately.
nf7::Future<std::shared_ptr<Lock>> AcquireLock(
const std::shared_ptr<nf7::Context>& ctx, bool ex = false) noexcept {
if (auto ret = TryAcquireLock(ctx, ex)) {
return {ret};
} else {
if (ex || pends_.size() == 0 || pends_.back().ex) {
pends_.push_back({.pro = {ctx}, .ctx = ctx, .ex = ex});
}
return pends_.back().pro.future();
}
}
std::shared_ptr<Lock> TryAcquireLock(
const std::shared_ptr<nf7::Context>& ctx, bool ex = false) noexcept {
auto k = TryAcquireLock_(ctx, ex);
if (k) {
onLock();
}
return k;
}
const char* status() const noexcept {
return sync_.expired()? "free": ex_? "exlocked": "locked";
}
size_t pendings() const noexcept {
return pends_.size();
}
std::function<void()> onLock = [](){};
std::function<void()> onUnlock = [](){};
private:
nf7::Life<Mutex> life_;
bool ex_ = false;
std::weak_ptr<Sync> sync_;
struct Item final {
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
std::shared_ptr<nf7::Context> ctx;
bool ex;
};
std::deque<Item> pends_;
std::shared_ptr<Lock> TryAcquireLock_(
const std::shared_ptr<nf7::Context>& ctx, bool ex) noexcept {
auto sync = sync_.lock();
if (sync) {
if (ex_ || ex) return nullptr;
} else {
sync = std::make_shared<Sync>(*this);
ex_ = ex;
sync_ = sync;
}
return std::make_shared<Mutex::Lock>(ctx, sync);
}
};
class Mutex::Sync {
public:
friend nf7::Mutex;
Sync() = delete;
Sync(nf7::Mutex& mtx) noexcept : mtx_(mtx.life_) {
}
Sync(const Sync&) = delete;
Sync(Sync&&) = delete;
Sync& operator=(const Sync&) = delete;
Sync& operator=(Sync&&) = delete;
~Sync() noexcept {
if (mtx_) {
auto& pends = mtx_->pends_;
if (pends.size() > 0) {
auto item = std::move(pends.front());
pends.pop_front();
mtx_->ex_ = false;
mtx_->sync_ = {};
auto k = mtx_->TryAcquireLock_(item.ctx, item.ex);
assert(k);
item.pro.Return(std::move(k));
} else {
mtx_->onUnlock();
}
}
}
private:
nf7::Life<nf7::Mutex>::Ref mtx_;
};
class Mutex::Lock {
public:
Lock(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<Mutex::Sync>& sync) noexcept :
ctx_(ctx), sync_(sync) {
}
Lock(const Lock&) = default;
Lock(Lock&&) = default;
Lock& operator=(const Lock&) = default;
Lock& operator=(Lock&&) = default;
~Lock() noexcept {
// Ensure that the Sync's destructor is called on worker thread.
ctx_->env().ExecSub(
ctx_, [sync = std::move(sync_)]() mutable { sync = nullptr; });
}
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<Mutex::Sync> sync_;
};
template <typename T>
class Mutex::Resource {
public:
Resource() = delete;
Resource(const std::shared_ptr<Mutex::Lock>& k, T&& v) noexcept :
lock_(k), value_(std::move(v)) {
}
Resource(const std::shared_ptr<Mutex::Lock>& k, const T& v) noexcept :
Resource(k, T {v}) {
}
Resource(const Resource&) = default;
Resource(Resource&&) = default;
Resource& operator=(const Resource&) = default;
Resource& operator=(Resource&&) = default;
T& operator*() noexcept { return value_; }
const T& operator*() const noexcept { return value_; }
T* operator->() noexcept { return &value_; }
const T* operator->() const noexcept { return &value_; }
const std::shared_ptr<Mutex::Lock>& lock() const noexcept { return lock_; }
const T& value() const noexcept { return value_; }
private:
std::shared_ptr<Mutex::Lock> lock_;
T value_;
};
} // 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,59 +0,0 @@
#pragma once
#include <functional>
#include <filesystem>
#include <optional>
#include <vector>
#include "common/file_base.hh"
namespace nf7 {
class NFileWatcher final : public nf7::FileBase::Feature {
public:
NFileWatcher() = delete;
NFileWatcher(nf7::FileBase& f) noexcept : nf7::FileBase::Feature(f) {
}
NFileWatcher(const NFileWatcher&) = delete;
NFileWatcher(NFileWatcher&&) = delete;
NFileWatcher& operator=(const NFileWatcher&) = delete;
NFileWatcher& operator=(NFileWatcher&&) = delete;
void Watch(const std::filesystem::path& npath) noexcept {
npaths_.push_back(npath);
lastmod_ = std::nullopt;
}
void Clear() noexcept {
npaths_.clear();
lastmod_ = std::nullopt;
}
std::function<void()> onMod;
protected:
void Update() noexcept override {
auto latest = std::filesystem::file_time_type::duration::min();
for (const auto& npath : npaths_) {
try {
const auto lastmod = std::filesystem::last_write_time(npath).time_since_epoch();
latest = std::max(latest, lastmod);
} catch (std::filesystem::filesystem_error&) {
}
}
if (!lastmod_) {
lastmod_ = latest;
}
if (*lastmod_ < latest) {
onMod();
lastmod_ = latest;
}
}
private:
std::vector<std::filesystem::path> npaths_;
std::optional<std::filesystem::file_time_type::duration> 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,144 +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,
};
using Flags = uint8_t;
struct Meta final {
public:
Meta() = default;
Meta(std::vector<std::string>&& i, std::vector<std::string>&& o) noexcept :
inputs(std::move(i)), outputs(std::move(o)) {
}
Meta(const std::vector<std::string>& i, const std::vector<std::string>& o) noexcept :
inputs(i), outputs(o) {
}
Meta(const Meta&) = default;
Meta(Meta&&) = default;
Meta& operator=(const Meta&) = default;
Meta& operator=(Meta&&) = default;
std::vector<std::string> inputs, outputs;
};
static void ValidateSockets(std::span<const std::string> v) {
for (auto itr = v.begin(); itr < v.end(); ++itr) {
if (v.end() != std::find(itr+1, v.end(), *itr)) {
throw nf7::Exception {"name duplication: "+*itr};
}
}
for (auto& s : v) {
nf7::File::Path::ValidateTerm(s);
}
}
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 { }
// don't call too often because causes heap allocation
virtual Meta GetMeta() const noexcept = 0;
Flags flags() const noexcept { return flags_; }
private:
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:
struct Msg final {
public:
Msg() = delete;
Msg(std::string&& n, nf7::Value&& v, std::shared_ptr<Lambda>&& s) noexcept :
name(std::move(n)), value(std::move(v)), sender(std::move(s)) {
}
Msg(std::string_view n, const nf7::Value& v, const std::shared_ptr<Lambda>& s) noexcept :
name(n), value(v), sender(s) {
}
Msg(const Msg&) = default;
Msg(Msg&&) = default;
Msg& operator=(const Msg&) = default;
Msg& operator=(Msg&&) = default;
std::string name;
nf7::Value value;
std::shared_ptr<Lambda> sender;
};
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(const Msg&) noexcept {
}
void Handle(std::string_view k, const nf7::Value& v,
const std::shared_ptr<nf7::Node::Lambda>& sender) noexcept {
return Handle({k, v, sender});
}
std::shared_ptr<Node::Lambda> parent() const noexcept { return parent_.lock(); }
private:
std::weak_ptr<Node::Lambda> parent_;
};
} // namespace nf7

View File

@ -1,135 +0,0 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <unordered_set>
#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;
inline std::unique_ptr<nf7::History::Command> CreateCommandToRemoveExpired(
const std::unordered_set<uint64_t>& ids) 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));
}
std::unique_ptr<nf7::History::Command> NodeLinkStore::CreateCommandToRemoveExpired(
const std::unordered_set<uint64_t>& ids) noexcept {
std::vector<std::unique_ptr<nf7::History::Command>> cmds;
for (const auto& lk : links_) {
if (!ids.contains(lk.src_id) || !ids.contains(lk.dst_id)) {
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,94 +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 NodeRootLambda : public nf7::Node::Lambda,
public std::enable_shared_from_this<NodeRootLambda> {
public:
using Pair = std::pair<std::string, nf7::Value>;
static std::shared_ptr<NodeRootLambda> Create(
const std::shared_ptr<nf7::Context>& ctx, nf7::Node& n) noexcept {
auto ret = std::make_shared<NodeRootLambda>(ctx->env(), ctx->initiator(), ctx);
ret->target_ = n.CreateLambda(ret);
return ret;
}
using nf7::Node::Lambda::Lambda;
~NodeRootLambda() noexcept {
Abort();
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
std::unique_lock<std::mutex> lk {mtx_};
if (names_.contains(in.name)) {
names_.clear();
if (auto pro = std::exchange(pro_, std::nullopt)) {
lk.unlock();
pro->Return({in.name, in.value});
}
} else {
q_.push_back({in.name, in.value});
}
}
// 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();
}
void Abort() noexcept override {
target_->Abort();
pro_ = std::nullopt;
}
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,59 +0,0 @@
#pragma once
#include <memory>
#include <typeinfo>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
namespace nf7 {
template <typename T>
concept PureNodeFile_LoggerRef =
requires (T& t, const std::shared_ptr<nf7::LoggerRef>& f) { t.log_ = f; };
template <typename T>
class PureNodeFile final : public nf7::FileBase, public nf7::Node {
public:
PureNodeFile(nf7::Env& env) noexcept :
nf7::FileBase(T::kType, env),
nf7::Node(nf7::Node::kNone) {
if constexpr (PureNodeFile_LoggerRef<T>) {
log_ = std::make_shared<nf7::LoggerRef>(*this);
}
}
PureNodeFile(nf7::Deserializer& ar) : PureNodeFile(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<nf7::PureNodeFile<T>>(env);
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override {
auto la = std::make_shared<T>(*this, parent);
if constexpr (PureNodeFile_LoggerRef<T>) {
la->log_ = log_;
}
return la;
}
nf7::Node::Meta GetMeta() const noexcept override {
return T::kMeta;
}
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::Node>(t).Select(this);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
};
} // 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,60 +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;
}
if (++tick_ <= 2) {
return false;
}
tick_ = 0;
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_;
uint8_t tick_ = 0;
};
} // namespace nf7

View File

@ -1,52 +0,0 @@
#pragma once
#include <chrono>
#include <iostream>
#include "nf7.hh"
namespace nf7 {
class Stopwatch final {
public:
struct Benchmark;
static nf7::Env::Time now() noexcept { return nf7::Env::Clock::now(); }
Stopwatch() noexcept : begin_(now()) {
}
Stopwatch(const Stopwatch&) = default;
Stopwatch(Stopwatch&&) = default;
Stopwatch& operator=(const Stopwatch&) = default;
Stopwatch& operator=(Stopwatch&&) = default;
auto dur() const noexcept {
return now() - begin_;
}
private:
nf7::Env::Time begin_;
};
inline std::ostream& operator << (std::ostream& out, const Stopwatch& sw) {
return out << std::chrono::duration_cast<std::chrono::microseconds>(sw.dur()).count() << " usecs";
}
struct Stopwatch::Benchmark final {
public:
Benchmark(const char* name) noexcept : name_(name) {
}
~Benchmark() noexcept {
std::cout << name_ << ": " << sw_ << std::endl;
}
Benchmark(const Benchmark&) = delete;
Benchmark(Benchmark&&) = delete;
Benchmark& operator=(const Benchmark&) = delete;
Benchmark& operator=(Benchmark&&) = delete;
private:
const char* name_;
Stopwatch sw_;
};
} // 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,102 +0,0 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
#include <tracy/Tracy.hpp>
#include "nf7.hh"
#include "common/stopwatch.hh"
#include "common/timed_queue.hh"
namespace nf7 {
// a thread emulation by tasks
template <typename Runner, typename Task>
class Thread final : public nf7::Context,
public std::enable_shared_from_this<Thread<Runner, Task>> {
public:
static constexpr auto kTaskDur = std::chrono::milliseconds {1};
Thread() = delete;
Thread(nf7::File& f, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
Thread(f.env(), f.id(), std::move(runner), exec) {
}
Thread(nf7::Env& env, nf7::File::Id id, Runner&& runner, nf7::Env::Executor exec = nf7::Env::kAsync) noexcept :
nf7::Context(env, id), runner_(std::move(runner)), exec_(exec) {
}
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)});
ExecNext(true /* = entry */);
}
void SetExecutor(nf7::Env::Executor exec) noexcept {
exec_ = exec;
}
size_t tasksDone() const noexcept { return tasks_done_; }
private:
using Pair = std::pair<std::shared_ptr<nf7::Context>, Task>;
Runner runner_;
std::atomic<nf7::Env::Executor> exec_;
nf7::TimedQueue<Pair> q_;
std::mutex mtx_;
bool working_ = false;
nf7::Env::Time scheduled_;
std::atomic<size_t> tasks_done_ = 0;
using std::enable_shared_from_this<Thread<Runner, Task>>::shared_from_this;
void ExecNext(bool entry = false) noexcept {
{
std::unique_lock<std::mutex> k {mtx_};
if (std::exchange(working_, true)) return;
}
auto self = shared_from_this();
if (!entry) {
ZoneScopedN("thread task execution");
for (nf7::Stopwatch sw; sw.dur() < kTaskDur; ++tasks_done_) {
auto t = q_.Pop();
if (t) {
runner_(std::move(t->second));
} else {
if constexpr (std::is_invocable_v<Runner>) {
runner_(); // idle task
}
break;
}
}
}
{
std::unique_lock<std::mutex> k {mtx_};
if (auto time = q_.next()) {
if (time <= nf7::Env::Clock::now() || time != scheduled_) {
scheduled_ = *time;
env().Exec(exec_, self, [this]() mutable { ExecNext(); }, *time);
}
}
working_ = false;
}
}
};
} // namespace nf7

View File

@ -1,79 +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;
}
bool idle(nf7::Env::Time now = nf7::Env::Clock::now()) const noexcept {
const auto t = next();
return !t || *t > now;
}
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_;
};
} // 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,370 +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? v: std::make_shared<std::vector<uint8_t>>()) { }
Value& operator=(const Vector& v) noexcept { value_ = v? v: std::make_shared<std::vector<uint8_t>>(); return *this; }
Value(const ConstVector& v) noexcept : value_(v? std::const_pointer_cast<std::vector<uint8_t>>(v): std::make_shared<std::vector<uint8_t>>()) { }
Value& operator=(const ConstVector& v) noexcept { value_ = v? std::const_pointer_cast<std::vector<uint8_t>>(v): std::make_shared<std::vector<uint8_t>>(); 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? v: std::make_shared<std::vector<TuplePair>>()) { }
Value& operator=(const Tuple& v) noexcept { value_ = v? v: std::make_shared<std::vector<TuplePair>>(); return *this; }
Value(const ConstTuple& v) noexcept : value_(v? v: std::make_shared<std::vector<TuplePair>>()) { }
Value& operator=(const ConstTuple& v) noexcept { value_ = v? v: std::make_shared<std::vector<TuplePair>>(); 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 { *this = std::move(v); }
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; }
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<ConstVector>(value_); }
bool isTuple() const noexcept { return std::holds_alternative<ConstTuple>(value_); }
bool isData() const noexcept { return std::holds_alternative<DataPtr>(value_); }
// direct accessors
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<ConstVector>(); }
const ConstTuple& tuple() const { return get<ConstTuple>(); }
const DataPtr& data() const { return get<DataPtr>(); }
const auto& value() const noexcept { return value_; }
// direct reference accessor
Integer& integer() { return get<Integer>(); }
Boolean& boolean() { return get<Boolean>(); }
Scalar& scalar() { return get<Scalar>(); }
String& string() { return get<String>(); }
// conversion accessor
template <typename N>
N integer() const {
return SafeCast<N>(integer());
}
template <typename N>
N scalar() const {
return SafeCast<N>(scalar());
}
template <typename N>
N integerOrScalar() const {
try {
return SafeCast<N>(integer());
} catch (nf7::Exception&) {
return SafeCast<N>(scalar());
}
}
template <typename N>
N scalarOrInteger() const {
try {
return SafeCast<N>(scalar());
} catch (nf7::Exception&) {
return SafeCast<N>(integer());
}
}
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");
}
// tuple element accessor
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});
}
Value tupleOr(auto idx, const Value& v) const noexcept {
try {
return tuple(idx);
} catch (nf7::Exception&) {
return v;
}
}
// extended accessor
nf7::File& file(const nf7::File& base) const {
if (isInteger()) {
return base.env().GetFileOrThrow(integerOrScalar<nf7::File::Id>());
} else if (isString()) {
return base.ResolveOrThrow(string());
} else {
throw IncompatibleException {"expected file id or file path"};
}
}
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()(ConstVector) noexcept { return "vector"; }
auto operator()(ConstTuple) noexcept { return "tuple"; }
auto operator()(DataPtr) noexcept { return "data"; }
};
return std::visit(Visitor {}, value_);
}
template <typename Ar>
Ar& serialize(Ar& ar) noexcept {
ar & value_;
return ar;
}
private:
std::variant<Pulse, Boolean, Integer, Scalar, String, ConstVector, ConstTuple, DataPtr> value_;
template <typename T>
const T& get() const {
return const_cast<Value&>(*this).get<T>();
}
template <typename T>
T& get()
try {
return std::get<T>(value_);
} catch (std::bad_variant_access&) {
std::stringstream st;
st << "expected " << typeid(T).name() << " but it's " << typeName();
throw IncompatibleException(st.str());
}
template <typename R, typename N>
static R SafeCast(N in) {
const auto ret = static_cast<R>(in);
const auto retn = static_cast<N>(ret);
if constexpr (std::is_unsigned<R>::value) {
if (in < 0) {
throw IncompatibleException("integer underflow");
}
}
if constexpr (std::is_integral<R>::value && std::is_integral<N>::value) {
if (in != retn) {
throw IncompatibleException("integer out of range");
}
}
if constexpr (std::is_integral<R>::value && std::is_floating_point<N>::value) {
if (std::max(retn, in) - std::min(retn, in) > 1) {
throw IncompatibleException("bad precision while conversion of floating point");
}
}
return ret;
}
};
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::ConstVector> {
public:
template <typename Archive>
static Archive& save(Archive&, const nf7::Value::ConstVector&) {
throw nf7::Exception("cannot serialize Value::Vector");
}
template <typename Archive>
static Archive& load(Archive&, nf7::Value::ConstVector&) {
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::ConstTuple> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::Value::ConstTuple& tup) {
ar(*tup);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::Value::ConstTuple& tup) {
auto ptr = std::make_shared<std::vector<nf7::Value::TuplePair>>();
ar(*ptr);
tup = std::move(ptr);
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,26 +0,0 @@
#pragma once
#include <string>
#include <yaml-cpp/yaml.h>
#include "nf7.hh"
namespace YAML {
template <>
struct convert<nf7::File::Path> {
static bool decode(const Node& node, nf7::File::Path& p)
try {
p = nf7::File::Path::Parse(node.as<std::string>());
return true;
} catch (nf7::Exception&) {
return false;
}
};
inline Emitter& operator<<(Emitter& st, const nf7::File::Path& p) {
return st << p.Stringify();
}
} // namespace nf7

View File

@ -1,42 +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;
}
};
#define NF7_YAS_DEFINE_ENUM_SERIALIZER(T) \
template <size_t F> \
struct serializer< \
yas::detail::type_prop::is_enum, \
yas::detail::ser_case::use_internal_serializer, \
F, T> : nf7::EnumSerializer<T> { \
}
} // 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,65 +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);
try {
typename Archive::ChunkGuard guard {ar};
f = nf7::File::registry(name).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,
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);
}

Some files were not shown because too many files have changed in this diff Show More