Compare commits
No commits in common. "v0.4.0" and "main" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
/build/
|
||||
/build*/
|
||||
|
230
CMakeLists.txt
230
CMakeLists.txt
@ -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
13
LICENSE
@ -1,13 +0,0 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
68
README.md
68
README.md
@ -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 --*
|
||||
|
@ -1,79 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class AggregateCommand : public nf7::History::Command {
|
||||
public:
|
||||
using CommandList = std::vector<std::unique_ptr<Command>>;
|
||||
|
||||
AggregateCommand(CommandList&& commands, bool applied = false) noexcept :
|
||||
commands_(std::move(commands)), applied_(applied) {
|
||||
}
|
||||
~AggregateCommand() noexcept {
|
||||
if (applied_) {
|
||||
for (auto itr = commands_.begin(); itr < commands_.end(); ++itr) {
|
||||
*itr = nullptr;
|
||||
}
|
||||
} else {
|
||||
for (auto itr = commands_.rbegin(); itr < commands_.rend(); ++itr) {
|
||||
*itr = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Apply() override {
|
||||
Exec(commands_.begin(), commands_.end(),
|
||||
[](auto& a) { a->Apply(); },
|
||||
[](auto& a) { a->Revert(); });
|
||||
applied_ = true;
|
||||
}
|
||||
void Revert() override {
|
||||
Exec(commands_.rbegin(), commands_.rend(),
|
||||
[](auto& a) { a->Revert(); },
|
||||
[](auto& a) { a->Apply(); });
|
||||
applied_ = false;
|
||||
}
|
||||
|
||||
std::span<const std::unique_ptr<Command>> commands() const noexcept { return commands_; }
|
||||
|
||||
private:
|
||||
CommandList commands_;
|
||||
|
||||
bool applied_;
|
||||
|
||||
|
||||
static void Exec(auto begin, auto end, const auto& apply, const auto& revert) {
|
||||
auto itr = begin;
|
||||
try {
|
||||
try {
|
||||
while (itr < end) {
|
||||
apply(*itr);
|
||||
++itr;
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
throw History::CorruptException("failed to revert AggregateCommand");
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
try {
|
||||
while (itr > begin) {
|
||||
--itr;
|
||||
revert(*itr);
|
||||
}
|
||||
} catch (History::CorruptException&) {
|
||||
throw History::CorruptException(
|
||||
"AggregateCommand gave up recovering from failure of revert");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Dir : public File::Interface {
|
||||
public:
|
||||
class DuplicateException;
|
||||
|
||||
Dir() = default;
|
||||
|
||||
virtual File& Add(std::string_view, std::unique_ptr<File>&&) = 0;
|
||||
virtual std::unique_ptr<File> Remove(std::string_view) noexcept = 0;
|
||||
};
|
||||
class Dir::DuplicateException : public Exception {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
417
common/future.hh
417
common/future.hh
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/history.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericHistory : public nf7::History {
|
||||
public:
|
||||
GenericHistory() = default;
|
||||
GenericHistory(const GenericHistory&) = delete;
|
||||
GenericHistory(GenericHistory&&) = default;
|
||||
GenericHistory& operator=(const GenericHistory&) = delete;
|
||||
GenericHistory& operator=(GenericHistory&&) = default;
|
||||
~GenericHistory() noexcept {
|
||||
Clear();
|
||||
}
|
||||
|
||||
Command& Add(std::unique_ptr<Command>&& cmd) noexcept override {
|
||||
cmds_.erase(cmds_.begin()+static_cast<intmax_t>(cursor_), cmds_.end());
|
||||
cmds_.push_back(std::move(cmd));
|
||||
cursor_++;
|
||||
return *cmds_.back();
|
||||
}
|
||||
void Clear() noexcept {
|
||||
for (size_t i = 0; i < cursor_; ++i) {
|
||||
cmds_[i] = nullptr;
|
||||
}
|
||||
for (size_t i = cmds_.size(); i > cursor_;) {
|
||||
--i;
|
||||
cmds_[i] = nullptr;
|
||||
}
|
||||
cmds_.clear();
|
||||
}
|
||||
|
||||
void UnDo() {
|
||||
if (cursor_ <= 0) return;
|
||||
cmds_[cursor_-1]->Revert();
|
||||
--cursor_;
|
||||
}
|
||||
void ReDo() {
|
||||
if (cursor_ >= cmds_.size()) return;
|
||||
cmds_[cursor_]->Apply();
|
||||
++cursor_;
|
||||
}
|
||||
|
||||
Command* prev() const noexcept {
|
||||
return cursor_ > 0? cmds_[cursor_-1].get(): nullptr;
|
||||
}
|
||||
Command* next() const noexcept {
|
||||
return cursor_ < cmds_.size()? cmds_[cursor_].get(): nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Command>> cmds_;
|
||||
|
||||
size_t cursor_ = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericWatcher final : public Env::Watcher {
|
||||
public:
|
||||
using Handler = std::function<void(const File::Event&)>;
|
||||
|
||||
GenericWatcher(Env& env) noexcept : Watcher(env) {
|
||||
}
|
||||
|
||||
void AddHandler(File::Event::Type type, Handler&& h) noexcept {
|
||||
handlers_[type] = std::move(h);
|
||||
}
|
||||
void Handle(const File::Event& ev) noexcept override {
|
||||
auto handler = handlers_.find(ev.type);
|
||||
if (handler == handlers_.end()) return;
|
||||
handler->second(ev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<File::Event::Type, Handler> handlers_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
457
common/gl_obj.cc
457
common/gl_obj.cc
@ -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
|
236
common/gl_obj.hh
236
common/gl_obj.hh
@ -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
|
@ -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
|
246
common/gui.cc
246
common/gui.cc
@ -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
|
@ -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
|
@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7::gui::dnd {
|
||||
|
||||
// data entity is char[] of file path
|
||||
constexpr const char* kFilePath = "nf7::File::Path";
|
||||
|
||||
|
||||
template <typename T>
|
||||
bool Send(const char* type, const T&) noexcept;
|
||||
|
||||
template <>
|
||||
inline bool Send<std::string>(const char* type, const std::string& v) noexcept {
|
||||
return ImGui::SetDragDropPayload(type, v.data(), v.size());
|
||||
}
|
||||
template <>
|
||||
inline bool Send<std::string_view>(const char* type, const std::string_view& v) noexcept {
|
||||
return ImGui::SetDragDropPayload(type, v.data(), v.size());
|
||||
}
|
||||
template <>
|
||||
inline bool Send<File::Path>(const char* type, const File::Path& p) noexcept {
|
||||
return Send(type, p.Stringify());
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T To(const ImGuiPayload&) noexcept;
|
||||
|
||||
template <>
|
||||
inline std::string To<std::string>(const ImGuiPayload& pay) noexcept {
|
||||
std::string ret;
|
||||
ret.resize(static_cast<size_t>(pay.DataSize));
|
||||
std::memcpy(ret.data(), pay.Data, ret.size());
|
||||
return ret;
|
||||
}
|
||||
template <>
|
||||
inline File::Path To<File::Path>(const ImGuiPayload& pay) noexcept {
|
||||
return File::Path::Parse(To<std::string>(pay));
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> Accept(const char* type, ImGuiDragDropFlags flags = 0) noexcept {
|
||||
if (auto pay = ImGui::AcceptDragDropPayload(type, flags)) {
|
||||
return To<T>(*pay);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
template <typename T>
|
||||
const ImGuiPayload* Peek(const char* type, auto& v, ImGuiDragDropFlags flags = 0) noexcept {
|
||||
flags |= ImGuiDragDropFlags_AcceptPeekOnly;
|
||||
if (auto pay = ImGui::AcceptDragDropPayload(type, flags)) {
|
||||
v = To<T>(*pay);
|
||||
return pay;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool IsFirstAccept() noexcept {
|
||||
const auto ctx = ImGui::GetCurrentContext();
|
||||
return ctx->DragDropAcceptFrameCount < ctx->FrameCount;
|
||||
}
|
||||
|
||||
inline void DrawRect() noexcept {
|
||||
auto& r = ImGui::GetCurrentContext()->DragDropTargetRect;
|
||||
ImGui::GetForegroundDrawList()->AddRect(
|
||||
r.Min - ImVec2 {3.5f, 3.5f},
|
||||
r.Max + ImVec2 {3.5f, 3.5f},
|
||||
ImGui::GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);
|
||||
}
|
||||
|
||||
} // namespace nf7::gui::dnd
|
@ -1,390 +0,0 @@
|
||||
#include "common/gui_timeline.hh"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
bool Timeline::Begin() noexcept {
|
||||
assert(frame_state_ == kRoot);
|
||||
layer_idx_ = 0;
|
||||
layer_y_ = 0;
|
||||
layer_h_ = 0;
|
||||
|
||||
layer_idx_first_display_ = 0;
|
||||
layer_offset_y_.clear();
|
||||
|
||||
scroll_x_to_mouse_ = false;
|
||||
scroll_y_to_mouse_ = false;
|
||||
|
||||
action_ = kNone;
|
||||
action_target_ = nullptr;
|
||||
|
||||
if (!ImGui::BeginChild(id_, {0, 0}, false, ImGuiWindowFlags_NoMove)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
body_offset_ = {headerWidth(), xgridHeight()};
|
||||
body_size_ = ImGui::GetContentRegionMax() - body_offset_;
|
||||
scroll_size_.x = std::max(body_size_.x, GetXFromTime(len_) + 16*ImGui::GetFontSize());
|
||||
|
||||
constexpr auto kFlags =
|
||||
ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoBackground;
|
||||
ImGui::SetCursorPos({body_offset_.x, 0});
|
||||
if (ImGui::BeginChild("xgrid", {body_size_.x, body_offset_.y}, false, kFlags)) {
|
||||
UpdateXGrid();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SetCursorPos({0, body_offset_.y});
|
||||
if (ImGui::BeginChild("layers", {0, 0}, false, kFlags)) {
|
||||
frame_state_ = kHeader;
|
||||
|
||||
ImGui::BeginGroup();
|
||||
return true;
|
||||
}
|
||||
ImGui::EndChild();
|
||||
return false;
|
||||
}
|
||||
void Timeline::End() noexcept {
|
||||
assert(frame_state_ == kRoot);
|
||||
ImGui::EndChild(); // end of root
|
||||
}
|
||||
|
||||
void Timeline::NextLayerHeader(Layer layer, float height) noexcept {
|
||||
assert(frame_state_ == kHeader);
|
||||
assert(height > 0);
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
if (layer_h_ > 0) {
|
||||
++layer_idx_;
|
||||
layer_y_ += layer_h_+padding()*2;
|
||||
}
|
||||
layer_h_ = height*em;
|
||||
layer_ = layer;
|
||||
|
||||
// save Y offset of the layer if shown
|
||||
if (layer_idx_first_display_) {
|
||||
if (layer_y_ < scroll_.y+body_size_.y) {
|
||||
layer_offset_y_.push_back(layer_y_);
|
||||
}
|
||||
} else {
|
||||
const auto bottom = layer_y_+layer_h_;
|
||||
if (bottom > scroll_.y) {
|
||||
layer_idx_first_display_ = layer_idx_;
|
||||
layer_offset_y_.push_back(layer_y_);
|
||||
}
|
||||
}
|
||||
|
||||
const auto mouse = ImGui::GetMousePos().y;
|
||||
if (layerTopScreenY() <= mouse && mouse < layerBottomScreenY()) {
|
||||
mouse_layer_ = layer;
|
||||
mouse_layer_y_ = layer_y_;
|
||||
mouse_layer_h_ = layer_h_;
|
||||
}
|
||||
|
||||
ImGui::SetCursorPos({0, std::round(layer_y_)});
|
||||
const auto col = ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);
|
||||
const auto spos = ImGui::GetCursorScreenPos();
|
||||
const auto size = ImGui::GetWindowSize();
|
||||
|
||||
auto d = ImGui::GetWindowDrawList();
|
||||
d->AddLine({spos.x, spos.y}, {spos.x+size.x, spos.y}, col);
|
||||
|
||||
ImGui::SetCursorPos({0, std::round(layer_y_+padding())});
|
||||
}
|
||||
|
||||
bool Timeline::BeginBody() noexcept {
|
||||
assert(frame_state_ == kHeader);
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
const auto ctx = ImGui::GetCurrentContext();
|
||||
const auto& io = ImGui::GetIO();
|
||||
|
||||
// end of header group
|
||||
ImGui::EndGroup();
|
||||
scroll_size_.y = ImGui::GetItemRectSize().y;
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (auto wh = ImGui::GetIO().MouseWheel) {
|
||||
scroll_.y -= wh * 5*em;
|
||||
}
|
||||
}
|
||||
|
||||
// beginnign of body
|
||||
constexpr auto kFlags = ImGuiWindowFlags_NoBackground;
|
||||
ImGui::SameLine(0, 0);
|
||||
if (ImGui::BeginChild("body", {0, scroll_size_.y}, false, kFlags)) {
|
||||
frame_state_ = kBody;
|
||||
body_screen_offset_ = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImGui::InvisibleButton(
|
||||
"viewport-grip", scroll_size_,
|
||||
ImGuiButtonFlags_MouseButtonMiddle |
|
||||
ImGuiButtonFlags_MouseButtonLeft);
|
||||
ImGui::SetItemAllowOverlap();
|
||||
if (ImGui::IsItemActive()) {
|
||||
switch (ctx->ActiveIdMouseButton) {
|
||||
case ImGuiMouseButton_Left: // click timeline to set time
|
||||
action_time_ = GetTimeFromScreenX(io.MousePos.x);
|
||||
if (ImGui::IsItemActivated() || action_time_ != action_last_set_time_) {
|
||||
action_ = kSetTime;
|
||||
action_last_set_time_ = action_time_;
|
||||
}
|
||||
break;
|
||||
|
||||
case ImGuiMouseButton_Middle: // easyscroll
|
||||
scroll_ -= io.MouseDelta;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
len_ = 0;
|
||||
layer_ = nullptr;
|
||||
layer_idx_ = 0;
|
||||
layer_y_ = 0;
|
||||
layer_h_ = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void Timeline::EndBody() noexcept {
|
||||
assert(frame_state_ == kBody);
|
||||
frame_state_ = kRoot;
|
||||
|
||||
const auto& io = ImGui::GetIO();
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
// manipulation by mouse
|
||||
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||
if (io.MouseWheel) {
|
||||
if (io.KeyCtrl) {
|
||||
const auto xscroll_base = scroll_.x/zoom_;
|
||||
|
||||
// zoom
|
||||
const auto zmin = 16.f / static_cast<float>(len_);
|
||||
zoom_ += (zoom_*.99f+.01f)*.1f*io.MouseWheel;
|
||||
zoom_ = std::clamp(zoom_, zmin, 1.f);
|
||||
|
||||
scroll_.x = xscroll_base * zoom_;
|
||||
} else {
|
||||
// x-scrolling
|
||||
scroll_.x -= io.MouseWheel * 2*em;
|
||||
}
|
||||
}
|
||||
}
|
||||
// move x scroll to the mouse
|
||||
if (scroll_x_to_mouse_) {
|
||||
const auto x = ImGui::GetMousePos().x-body_screen_offset_.x;
|
||||
if (x < scroll_.x+2*em) {
|
||||
scroll_.x = x-2*em;
|
||||
} else {
|
||||
const auto right = scroll_.x+body_size_.x - 2*em;
|
||||
if (x > right) {
|
||||
scroll_.x += x-right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll_.x = std::clamp(scroll_.x, 0.f, std::max(0.f, scroll_size_.x-body_size_.x));
|
||||
ImGui::SetScrollX(scroll_.x);
|
||||
ImGui::EndChild();
|
||||
|
||||
// move y scroll to the mouse
|
||||
if (scroll_y_to_mouse_ && mouse_layer_) {
|
||||
if (mouse_layer_y_ < scroll_.y) {
|
||||
scroll_.y = mouse_layer_y_;
|
||||
} else {
|
||||
const auto bottom = mouse_layer_y_+mouse_layer_h_;
|
||||
if (bottom > scroll_.y+body_size_.y) {
|
||||
scroll_.y = bottom-body_size_.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll_.y = std::clamp(scroll_.y, 0.f, std::max(0.f, scroll_size_.y-body_size_.y));
|
||||
ImGui::SetScrollY(scroll_.y);
|
||||
ImGui::EndChild(); // end of layers
|
||||
}
|
||||
bool Timeline::NextLayer(Layer layer, float height) noexcept {
|
||||
assert(frame_state_ == kBody);
|
||||
assert(height > 0);
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
if (layer_h_ > 0) {
|
||||
++layer_idx_;
|
||||
layer_y_ += layer_h_+padding()*2;
|
||||
}
|
||||
layer_h_ = height*em;
|
||||
layer_ = layer;
|
||||
|
||||
// it's shown if y offset is saved
|
||||
return !!layerTopY(layer_idx_);
|
||||
}
|
||||
|
||||
bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept {
|
||||
assert(frame_state_ == kBody);
|
||||
frame_state_ = kItem;
|
||||
|
||||
len_ = std::max(len_, end);
|
||||
item_ = item;
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
const auto pad = padding();
|
||||
const auto left = GetXFromTime(begin);
|
||||
const auto right = GetXFromTime(end);
|
||||
|
||||
const auto w = std::max(1.f, right-left);
|
||||
const auto h = layer_h_;
|
||||
|
||||
ImGui::SetCursorPos({left, std::round(layer_y_+pad)});
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0, 0});
|
||||
constexpr auto kFlags =
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse;
|
||||
const bool shown = ImGui::BeginChild(ImGui::GetID(item), {w, h}, true, kFlags);
|
||||
ImGui::PopStyleVar(1);
|
||||
|
||||
if (shown) {
|
||||
const auto resizer_w = std::min(1*em, w/2);
|
||||
|
||||
ImGui::SetCursorPos({0, 0});
|
||||
ImGui::InvisibleButton("begin", {resizer_w, h});
|
||||
ImGui::SetItemAllowOverlap();
|
||||
HandleGrip(item, 0, kResizeBegin, kResizeBeginDone, ImGuiMouseCursor_ResizeEW);
|
||||
|
||||
ImGui::SetCursorPos({w-resizer_w, 0});
|
||||
ImGui::InvisibleButton("end", {resizer_w, h});
|
||||
ImGui::SetItemAllowOverlap();
|
||||
HandleGrip(item, -resizer_w, kResizeEnd, kResizeEndDone, ImGuiMouseCursor_ResizeEW);
|
||||
|
||||
const auto mover_w = std::max(1.f, w-resizer_w*2);
|
||||
ImGui::SetCursorPos({resizer_w, 0});
|
||||
ImGui::InvisibleButton("mover", {mover_w, h});
|
||||
ImGui::SetItemAllowOverlap();
|
||||
HandleGrip(item, resizer_w, kMove, kMoveDone, ImGuiMouseCursor_Hand);
|
||||
|
||||
const auto wpad = ImGui::GetStyle().WindowPadding / 2;
|
||||
ImGui::SetCursorPosY(wpad.y);
|
||||
ImGui::Indent(wpad.x);
|
||||
}
|
||||
return shown;
|
||||
}
|
||||
void Timeline::EndItem() noexcept {
|
||||
assert(frame_state_ == kItem);
|
||||
frame_state_ = kBody;
|
||||
ImGui::Unindent();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept {
|
||||
const auto d = ImGui::GetWindowDrawList();
|
||||
|
||||
const auto spos = ImGui::GetWindowPos();
|
||||
const auto size = ImGui::GetWindowSize();
|
||||
const auto grid_h = xgridHeight();
|
||||
const auto x = GetScreenXFromTime(t);
|
||||
if (x < body_screen_offset_.x || x > body_screen_offset_.x+body_size_.x) return;
|
||||
|
||||
d->AddLine({x, spos.y}, {x, spos.y+size.y}, col);
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
const auto num = std::to_string(t);
|
||||
d->AddText({x, spos.y + grid_h*0.1f }, col, num.c_str());
|
||||
d->AddText({x, spos.y + grid_h*0.1f+em}, col, name);
|
||||
}
|
||||
void Timeline::Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept {
|
||||
const auto d = ImGui::GetWindowDrawList();
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
const auto x = GetScreenXFromTime(t);
|
||||
if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return;
|
||||
|
||||
const auto y = layerTopScreenY(layer);
|
||||
if (!y || *y < scroll_.y) return;
|
||||
|
||||
d->AddTriangleFilled({x, *y}, {x+em, *y-em/2}, {x+em, *y+em/2}, col);
|
||||
}
|
||||
|
||||
void Timeline::UpdateXGrid() noexcept {
|
||||
constexpr uint64_t kAccentInterval = 5;
|
||||
|
||||
const uint64_t unit_min = static_cast<uint64_t>(1/zoom_);
|
||||
uint64_t unit = 1;
|
||||
while (unit < unit_min) unit *= 10;
|
||||
|
||||
const auto spos = ImGui::GetWindowPos();
|
||||
const auto size = ImGui::GetContentRegionMax();
|
||||
const auto color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
|
||||
const auto left = GetTimeFromX(scroll_.x)/unit*unit;
|
||||
const auto right = GetTimeFromX(scroll_.x+body_size_.x)+1;
|
||||
|
||||
const auto d = ImGui::GetWindowDrawList();
|
||||
|
||||
for (uint64_t t = left; t < right; t += unit) {
|
||||
const bool accent = !((t/unit)%kAccentInterval);
|
||||
|
||||
const auto x = GetScreenXFromTime(t);
|
||||
const auto y = spos.y + size.y;
|
||||
const auto h = accent? size.y*0.2f: size.y*0.1f;
|
||||
d->AddLine({x, y}, {x, y-h}, color);
|
||||
|
||||
if (accent) {
|
||||
const auto num = std::to_string(t);
|
||||
const auto num_size = ImGui::CalcTextSize(num.c_str());
|
||||
d->AddText({x - num_size.x/2, y-h - num_size.y}, color, num.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
void Timeline::HandleGrip(Item item, float off, Action ac, Action acdone, ImGuiMouseCursor cur) noexcept {
|
||||
auto ctx = ImGui::GetCurrentContext();
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
if (ImGui::IsItemActive()) {
|
||||
if (ImGui::IsItemActivated()) {
|
||||
action_grip_moved_ = false;
|
||||
} else {
|
||||
action_ = ac;
|
||||
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0) {
|
||||
action_grip_moved_ = true;
|
||||
}
|
||||
}
|
||||
action_target_ = item;
|
||||
ImGui::SetMouseCursor(cur);
|
||||
|
||||
off -= 1;
|
||||
off += ctx->ActiveIdClickOffset.x;
|
||||
|
||||
const auto pos = ImGui::GetMousePos() - ImVec2{off, 0};
|
||||
action_time_ = GetTimeFromScreenX(pos.x);
|
||||
|
||||
scroll_x_to_mouse_ = true;
|
||||
scroll_y_to_mouse_ = (ac == kMove);
|
||||
|
||||
} else {
|
||||
if (ImGui::IsItemDeactivated()) {
|
||||
action_ = action_grip_moved_? acdone: kSelect;
|
||||
action_target_ = item;
|
||||
}
|
||||
if (ctx->LastItemData.ID == ctx->HoveredIdPreviousFrame) {
|
||||
ImGui::SetMouseCursor(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,206 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/utility/usertype.hpp>
|
||||
|
||||
#include "common/yas_imgui.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
// if (tl.Begin()) {
|
||||
// tl.NextLayerHeader(layer1, &layer1_height)
|
||||
// ImGui::Button("layer1");
|
||||
// tl.NextLayerHeader(layer2, &layer2_height)
|
||||
// ImGui::Button("layer2");
|
||||
//
|
||||
// if (tl.BeginBody()) {
|
||||
// tl.NextLayer(layer1, &layer);
|
||||
// if (tl.BeginItem(layer1_item1, 0, 10)) {
|
||||
// // update item
|
||||
// }
|
||||
// tl.EndItem();
|
||||
// if (tl.BeginItem(layer1_item2, 0, 10)) {
|
||||
// // update item
|
||||
// }
|
||||
// tl.EndItem();
|
||||
//
|
||||
// tl.NextLayer(layer2, &layer);
|
||||
// if (tl.BeginItem(layer2_item1, 0, 10)) {
|
||||
// // update item
|
||||
// }
|
||||
// tl.EndItem();
|
||||
// if (tl.BeginItem(layer2_item2, 0, 10)) {
|
||||
// // update item
|
||||
// }
|
||||
// tl.EndItem();
|
||||
// }
|
||||
// tl_.EndBody();
|
||||
//
|
||||
// tl_.Cursor(...);
|
||||
// tl_.Cursor(...);
|
||||
//
|
||||
// // handle actions
|
||||
// }
|
||||
// tl.End();
|
||||
|
||||
struct Timeline {
|
||||
public:
|
||||
enum Action {
|
||||
kNone,
|
||||
kSelect,
|
||||
kResizeBegin,
|
||||
kResizeBeginDone,
|
||||
kResizeEnd,
|
||||
kResizeEndDone,
|
||||
kMove,
|
||||
kMoveDone,
|
||||
kSetTime,
|
||||
};
|
||||
using Layer = void*;
|
||||
using Item = void*;
|
||||
|
||||
Timeline() = delete;
|
||||
Timeline(const char* id) noexcept : id_(id) {
|
||||
}
|
||||
Timeline(const Timeline&) = default;
|
||||
Timeline(Timeline&&) = delete;
|
||||
Timeline& operator=(const Timeline&) = default;
|
||||
Timeline& operator=(Timeline&&) = delete;
|
||||
|
||||
template <typename Ar>
|
||||
void serialize(Ar& ar) {
|
||||
ar(header_width_);
|
||||
ar(xgrid_height_);
|
||||
ar(zoom_);
|
||||
ar(padding_);
|
||||
ar(scroll_);
|
||||
}
|
||||
|
||||
bool Begin() noexcept;
|
||||
void End() noexcept;
|
||||
|
||||
void NextLayerHeader(Layer layer, float height) noexcept;
|
||||
|
||||
bool BeginBody() noexcept;
|
||||
void EndBody() noexcept;
|
||||
bool NextLayer(Layer layer, float height) noexcept;
|
||||
|
||||
bool BeginItem(Item item, uint64_t begin, uint64_t end) noexcept;
|
||||
void EndItem() noexcept;
|
||||
|
||||
void Cursor(const char*, uint64_t t, uint32_t col) noexcept;
|
||||
void Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept;
|
||||
|
||||
uint64_t GetTimeFromX(float x) const noexcept {
|
||||
return static_cast<uint64_t>(std::max(0.f, x/ImGui::GetFontSize()/zoom_));
|
||||
}
|
||||
uint64_t GetTimeFromScreenX(float x) const noexcept {
|
||||
return GetTimeFromX(x - body_screen_offset_.x);
|
||||
}
|
||||
float GetXFromTime(uint64_t t) const noexcept {
|
||||
return static_cast<float>(t)*zoom_*ImGui::GetFontSize();
|
||||
}
|
||||
float GetScreenXFromTime(uint64_t t) const noexcept {
|
||||
return GetXFromTime(t)+body_screen_offset_.x;
|
||||
}
|
||||
|
||||
float zoom() const noexcept { return zoom_; }
|
||||
float headerWidth() const noexcept { return header_width_*ImGui::GetFontSize(); }
|
||||
float xgridHeight() const noexcept { return xgrid_height_*ImGui::GetFontSize(); }
|
||||
float padding() const noexcept { return padding_*ImGui::GetFontSize(); }
|
||||
|
||||
std::optional<float> layerTopY(size_t idx) noexcept {
|
||||
if (!layer_idx_first_display_ || idx < *layer_idx_first_display_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
idx -= *layer_idx_first_display_;
|
||||
if (idx >= layer_offset_y_.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return layer_offset_y_[idx];
|
||||
}
|
||||
|
||||
std::optional<float> layerTopScreenY(size_t idx) noexcept {
|
||||
auto y = layerTopY(idx);
|
||||
if (!y) return std::nullopt;
|
||||
return *y + body_screen_offset_.y;
|
||||
}
|
||||
float layerTopScreenY() noexcept {
|
||||
return body_screen_offset_.y + layer_y_;
|
||||
}
|
||||
float layerBottomScreenY() noexcept {
|
||||
return layerTopScreenY() + layerH() + padding()*2;
|
||||
}
|
||||
float layerH() noexcept {
|
||||
return layer_h_;
|
||||
}
|
||||
|
||||
Layer mouseLayer() const noexcept { return mouse_layer_; }
|
||||
uint64_t mouseTime() const noexcept {
|
||||
return GetTimeFromScreenX(ImGui::GetMousePos().x);
|
||||
}
|
||||
|
||||
Action action() const noexcept { return action_; }
|
||||
Item actionTarget() const noexcept { return action_target_; }
|
||||
uint64_t actionTime() const noexcept { return action_time_; }
|
||||
|
||||
private:
|
||||
// immutable params
|
||||
const char* id_;
|
||||
|
||||
// permanentized params
|
||||
float header_width_ = 4.f;
|
||||
float xgrid_height_ = 4.f;
|
||||
float zoom_ = 1.f;
|
||||
float padding_ = 0.2f;
|
||||
ImVec2 scroll_;
|
||||
|
||||
// temporary values (immutable on each frame)
|
||||
ImVec2 body_size_;
|
||||
ImVec2 body_offset_;
|
||||
ImVec2 body_screen_offset_;
|
||||
|
||||
// volatile params
|
||||
enum {kRoot, kHeader, kBody, kItem} frame_state_ = kRoot;
|
||||
|
||||
uint64_t len_ = 0;
|
||||
ImVec2 scroll_size_;
|
||||
bool scroll_x_to_mouse_;
|
||||
bool scroll_y_to_mouse_;
|
||||
|
||||
|
||||
Layer mouse_layer_;
|
||||
float mouse_layer_y_;
|
||||
float mouse_layer_h_;
|
||||
|
||||
Layer layer_;
|
||||
size_t layer_idx_;
|
||||
float layer_y_;
|
||||
float layer_h_;
|
||||
|
||||
std::optional<size_t> layer_idx_first_display_;
|
||||
std::vector<float> layer_offset_y_;
|
||||
|
||||
Item item_;
|
||||
|
||||
Action action_;
|
||||
Item action_target_;
|
||||
uint64_t action_time_;
|
||||
bool action_grip_moved_;
|
||||
|
||||
uint64_t action_last_set_time_ = UINT64_MAX; // for kSetTime
|
||||
|
||||
|
||||
void UpdateXGrid() noexcept;
|
||||
void HandleGrip(
|
||||
Item item, float off, Action ac, Action acdone, ImGuiMouseCursor cur) noexcept;
|
||||
};
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,115 +0,0 @@
|
||||
#include "common/gui_value.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
bool Value::ReplaceType(Type t) noexcept {
|
||||
if (type_ == t) return false;
|
||||
|
||||
type_ = t;
|
||||
switch (type_) {
|
||||
case nf7::gui::Value::kPulse:
|
||||
entity_ = nf7::Value::Pulse {};
|
||||
break;
|
||||
case nf7::gui::Value::kInteger:
|
||||
entity_ = nf7::Value::Integer {0};
|
||||
break;
|
||||
case nf7::gui::Value::kScalar:
|
||||
case nf7::gui::Value::kNormalizedScalar:
|
||||
entity_ = nf7::Value::Scalar {0};
|
||||
break;
|
||||
case nf7::gui::Value::kString:
|
||||
case nf7::gui::Value::kMultilineString:
|
||||
entity_ = nf7::Value::String {};
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Value::ValidateValue() const {
|
||||
bool valid = true;
|
||||
switch (type_) {
|
||||
case nf7::gui::Value::kPulse:
|
||||
valid = entity_.isPulse();
|
||||
break;
|
||||
case nf7::gui::Value::kInteger:
|
||||
valid = entity_.isInteger();
|
||||
break;
|
||||
case nf7::gui::Value::kScalar:
|
||||
case nf7::gui::Value::kNormalizedScalar:
|
||||
valid = entity_.isScalar();
|
||||
break;
|
||||
case nf7::gui::Value::kString:
|
||||
case nf7::gui::Value::kMultilineString:
|
||||
valid = entity_.isString();
|
||||
break;
|
||||
}
|
||||
if (!valid) {
|
||||
throw nf7::DeserializeException {"invalid entity type"};
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::UpdateTypeButton(const char* name, bool small) noexcept {
|
||||
if (name == nullptr) {
|
||||
name = StringifyShortType(type_);
|
||||
}
|
||||
|
||||
if (small) {
|
||||
ImGui::SmallButton(name);
|
||||
} else {
|
||||
ImGui::Button(name);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
for (const auto t : kTypes) {
|
||||
if (ImGui::MenuItem(StringifyType(t), nullptr, type_ == t)) {
|
||||
ret |= ReplaceType(t);
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Value::UpdateEditor() noexcept {
|
||||
bool ret = false;
|
||||
const auto w = ImGui::CalcItemWidth();
|
||||
|
||||
const auto em = ImGui::GetFontSize();
|
||||
switch (type_) {
|
||||
case kPulse:
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button("PULSE", {w, 0});
|
||||
ImGui::EndDisabled();
|
||||
break;
|
||||
case kInteger:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_S64, &entity_.integer());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kScalar:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kNormalizedScalar:
|
||||
ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kString:
|
||||
ImGui::InputTextWithHint("##value", "string", &entity_.string());
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
case kMultilineString:
|
||||
ImGui::InputTextMultiline("##value", &entity_.string(), {w, 2.4f*em});
|
||||
ret |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace nf7::gui
|
@ -1,122 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7::gui {
|
||||
|
||||
class Value {
|
||||
public:
|
||||
enum Type {
|
||||
kPulse, kInteger, kScalar, kNormalizedScalar, kString, kMultilineString,
|
||||
};
|
||||
static inline const Type kTypes[] = {
|
||||
kPulse, kInteger, kScalar, kNormalizedScalar, kString, kMultilineString,
|
||||
};
|
||||
|
||||
static const char* StringifyType(Type t) noexcept {
|
||||
switch (t) {
|
||||
case kPulse: return "Pulse";
|
||||
case kInteger: return "Integer";
|
||||
case kScalar: return "Scalar";
|
||||
case kNormalizedScalar: return "NormalizedScalar";
|
||||
case kString: return "String";
|
||||
case kMultilineString: return "MultilineString";
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
static const char* StringifyShortType(Type t) noexcept {
|
||||
switch (t) {
|
||||
case kPulse: return "Pulse";
|
||||
case kInteger: return "Integer";
|
||||
case kScalar: return "Scalar";
|
||||
case kNormalizedScalar: return "NScalar";
|
||||
case kString: return "String";
|
||||
case kMultilineString: return "MString";
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
static Type ParseType(std::string_view v) {
|
||||
return
|
||||
v == "Pulse"? kPulse:
|
||||
v == "Integer"? kInteger:
|
||||
v == "Scalar"? kScalar:
|
||||
v == "NormalizedScalar"? kNormalizedScalar:
|
||||
v == "String"? kString:
|
||||
v == "MultilineString"? kMultilineString:
|
||||
throw nf7::DeserializeException {"unknown type: "+std::string {v}};
|
||||
}
|
||||
|
||||
Value() = default;
|
||||
Value(const Value&) = default;
|
||||
Value(Value&&) = default;
|
||||
Value& operator=(const Value&) = default;
|
||||
Value& operator=(Value&&) = default;
|
||||
|
||||
bool ReplaceType(Type t) noexcept;
|
||||
|
||||
void ReplaceEntity(const nf7::Value& v) {
|
||||
entity_ = v;
|
||||
ValidateValue();
|
||||
}
|
||||
void ReplaceEntity(nf7::Value&& v) {
|
||||
entity_ = std::move(v);
|
||||
ValidateValue();
|
||||
}
|
||||
void ValidateValue() const;
|
||||
|
||||
bool UpdateTypeButton(const char* name = nullptr, bool small = false) noexcept;
|
||||
bool UpdateEditor() noexcept;
|
||||
|
||||
Type type() const noexcept { return type_; }
|
||||
const nf7::Value& entity() const noexcept { return entity_; }
|
||||
|
||||
private:
|
||||
Type type_ = kInteger;
|
||||
nf7::Value entity_ = nf7::Value::Integer {0};
|
||||
};
|
||||
|
||||
} // namespace nf7::gui
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
nf7::gui::Value> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const nf7::gui::Value& v) {
|
||||
ar(std::string_view {v.StringifyType(v.type())}, v.entity());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, nf7::gui::Value& v) {
|
||||
std::string type;
|
||||
nf7::Value entity;
|
||||
ar(type, entity);
|
||||
|
||||
v.ReplaceType(v.ParseType(type));
|
||||
v.ReplaceEntity(entity);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,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
|
@ -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
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class History {
|
||||
public:
|
||||
class Command;
|
||||
class CorruptException;
|
||||
|
||||
History() = default;
|
||||
virtual ~History() = default;
|
||||
History(const History&) = delete;
|
||||
History(History&&) = delete;
|
||||
History& operator=(const History&) = delete;
|
||||
History& operator=(History&&) = delete;
|
||||
|
||||
virtual Command& Add(std::unique_ptr<Command>&&) noexcept = 0;
|
||||
|
||||
virtual void UnDo() = 0;
|
||||
virtual void ReDo() = 0;
|
||||
};
|
||||
|
||||
class History::Command {
|
||||
public:
|
||||
Command() = default;
|
||||
virtual ~Command() = default;
|
||||
Command(const Command&) = delete;
|
||||
Command(Command&&) = delete;
|
||||
Command& operator=(const Command&) = delete;
|
||||
Command& operator=(Command&&) = delete;
|
||||
|
||||
virtual void Apply() = 0;
|
||||
virtual void Revert() = 0;
|
||||
|
||||
void ExecApply(const std::shared_ptr<nf7::Context>& ctx) noexcept {
|
||||
ctx->env().ExecSub(ctx, [this]() { Apply(); });
|
||||
}
|
||||
void ExecRevert(const std::shared_ptr<nf7::Context>& ctx) noexcept {
|
||||
ctx->env().ExecSub(ctx, [this]() { Revert(); });
|
||||
}
|
||||
};
|
||||
|
||||
class History::CorruptException : public Exception {
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename T>
|
||||
class Life final {
|
||||
public:
|
||||
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
|
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <source_location.hh>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Logger : public File::Interface {
|
||||
public:
|
||||
enum Level {
|
||||
kTrace,
|
||||
kInfo,
|
||||
kWarn,
|
||||
kError,
|
||||
};
|
||||
struct Item;
|
||||
|
||||
Logger() = default;
|
||||
|
||||
// thread-safe
|
||||
virtual void Write(Item&&) noexcept = 0;
|
||||
|
||||
// The parameter is to avoid problems with multi-inheritance and nothing more than.
|
||||
virtual std::shared_ptr<Logger> self(Logger* = nullptr) noexcept = 0;
|
||||
};
|
||||
struct Logger::Item final {
|
||||
public:
|
||||
Item(Level lv, std::string_view m, File::Id f = 0, std::source_location s = std::source_location::current()) noexcept :
|
||||
level(lv), msg(m), file(f), srcloc(s) {
|
||||
}
|
||||
Item(const Item&) = default;
|
||||
Item(Item&&) = default;
|
||||
Item& operator=(const Item&) = default;
|
||||
Item& operator=(Item&&) = default;
|
||||
|
||||
Level level;
|
||||
std::string msg;
|
||||
|
||||
File::Id file;
|
||||
std::source_location srcloc;
|
||||
|
||||
std::exception_ptr ex;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include <source_location.hh>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/file_base.hh"
|
||||
#include "common/logger.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class LoggerRef final : public nf7::FileBase::Feature {
|
||||
public:
|
||||
LoggerRef(nf7::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
|
525
common/luajit.cc
525
common/luajit.cc
@ -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
|
148
common/luajit.hh
148
common/luajit.hh
@ -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
|
@ -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
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
class Queue : public File::Interface {
|
||||
public:
|
||||
using Task = std::function<void(lua_State*)>;
|
||||
|
||||
static constexpr auto kPath = "$/_luajit";
|
||||
|
||||
Queue() = default;
|
||||
Queue(const Queue&) = delete;
|
||||
Queue(Queue&&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
Queue& operator=(Queue&&) = delete;
|
||||
|
||||
// thread-safe
|
||||
virtual void Push(
|
||||
const std::shared_ptr<nf7::Context>&, Task&&, nf7::Env::Time t = {}) noexcept = 0;
|
||||
|
||||
virtual std::shared_ptr<Queue> self() noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7::luajit
|
@ -1,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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
169
common/mutex.hh
169
common/mutex.hh
@ -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
|
@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class NFile final {
|
||||
public:
|
||||
class Exception final : public nf7::Exception {
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
enum Flag : uint8_t {
|
||||
kRead = 1 << 0,
|
||||
kWrite = 1 << 1,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
NFile() = delete;
|
||||
NFile(const std::filesystem::path& path, Flags flags) :
|
||||
path_(path), flags_(flags) {
|
||||
Init();
|
||||
}
|
||||
~NFile() noexcept;
|
||||
NFile(const NFile&) = delete;
|
||||
NFile(NFile&&) = delete;
|
||||
NFile& operator=(const NFile&) = delete;
|
||||
NFile& operator=(NFile&&) = delete;
|
||||
|
||||
size_t Read(size_t offset, uint8_t* buf, size_t size);
|
||||
size_t Write(size_t offset, const uint8_t* buf, size_t size);
|
||||
size_t Truncate(size_t size);
|
||||
|
||||
Flags flags() const noexcept {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::filesystem::path path_;
|
||||
const Flags flags_;
|
||||
|
||||
uintptr_t handle_;
|
||||
|
||||
void Init();
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,69 +0,0 @@
|
||||
#include "common/nfile.hh"
|
||||
|
||||
extern "C" {
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
}
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
void NFile::Init() {
|
||||
int flags = 0;
|
||||
if ((flags_ & kRead) && (flags_ & kWrite)) {
|
||||
flags |= O_RDWR | O_CREAT;
|
||||
} else if (flags_ & kRead) {
|
||||
flags |= O_RDONLY;
|
||||
} else if (flags_ & kWrite) {
|
||||
flags |= O_WRONLY | O_CREAT;
|
||||
}
|
||||
|
||||
int fd = open(path_.string().c_str(), flags, 0600);
|
||||
if (fd < 0) {
|
||||
throw NFile::Exception {"open failure"};
|
||||
}
|
||||
handle_ = static_cast<uint64_t>(fd);
|
||||
}
|
||||
NFile::~NFile() noexcept {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
if (close(fd) == -1) {
|
||||
// ;(
|
||||
}
|
||||
}
|
||||
|
||||
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
const auto off = static_cast<off_t>(offset);
|
||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||
throw NFile::Exception {"lseek failure"};
|
||||
}
|
||||
const auto ret = read(fd, buf, size);
|
||||
if (ret == -1) {
|
||||
throw NFile::Exception {"read failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
const auto off = static_cast<off_t>(offset);
|
||||
if (lseek(fd, off, SEEK_SET) == off-1) {
|
||||
throw nf7::NFile::Exception {"lseek failure"};
|
||||
}
|
||||
const auto ret = write(fd, buf, size);
|
||||
if (ret == -1) {
|
||||
throw nf7::NFile::Exception {"write failure"};
|
||||
}
|
||||
return static_cast<size_t>(ret);
|
||||
}
|
||||
size_t NFile::Truncate(size_t size) {
|
||||
const auto fd = static_cast<int>(handle_);
|
||||
if (ftruncate(fd, static_cast<off_t>(size)) == 0) {
|
||||
throw nf7::NFile::Exception {"ftruncate failure"};
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
144
common/node.hh
144
common/node.hh
@ -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
|
@ -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
|
@ -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
|
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
template <typename Base, typename... I>
|
||||
struct PtrSelector final {
|
||||
public:
|
||||
PtrSelector(const std::type_info& t) noexcept : type_(&t) { }
|
||||
PtrSelector(const PtrSelector&) = delete;
|
||||
PtrSelector(PtrSelector&&) = delete;
|
||||
PtrSelector& operator=(const PtrSelector&) = delete;
|
||||
PtrSelector& operator=(PtrSelector&&) = delete;
|
||||
|
||||
template <typename T1, typename... T2>
|
||||
Base* Select(T1 ptr1, T2... ptr2) noexcept {
|
||||
auto ptr = Get<T1, I...>(ptr1);
|
||||
return ptr? ptr: Select(ptr2...);
|
||||
}
|
||||
Base* Select() noexcept { return nullptr; }
|
||||
|
||||
private:
|
||||
template <typename T, typename I1, typename... I2>
|
||||
Base* Get(T ptr) const noexcept {
|
||||
if constexpr (std::is_base_of<I1, std::remove_pointer_t<T>>::value) {
|
||||
if (*type_ == typeid(I1)) return static_cast<I1*>(ptr);
|
||||
}
|
||||
return Get<T, I2...>(ptr);
|
||||
}
|
||||
template <typename T>
|
||||
Base* Get(T) const noexcept { return nullptr; }
|
||||
|
||||
const std::type_info* const type_;
|
||||
};
|
||||
|
||||
template <typename... I>
|
||||
using InterfaceSelector = PtrSelector<File::Interface, I...>;
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
// thread-safe std::deque wrapper
|
||||
template <typename T>
|
||||
class Queue {
|
||||
public:
|
||||
Queue() = default;
|
||||
Queue(const Queue&) = delete;
|
||||
Queue(Queue&&) = delete;
|
||||
Queue& operator=(const Queue&) = delete;
|
||||
Queue& operator=(Queue&&) = delete;
|
||||
|
||||
void Push(T&& task) noexcept {
|
||||
std::unique_lock<std::mutex> _(mtx_);
|
||||
++n_;
|
||||
tasks_.push_back(std::move(task));
|
||||
}
|
||||
void Interrupt(T&& task) noexcept {
|
||||
std::unique_lock<std::mutex> _(mtx_);
|
||||
++n_;
|
||||
tasks_.push_front(std::move(task));
|
||||
}
|
||||
std::optional<T> Pop() noexcept {
|
||||
std::unique_lock<std::mutex> k(mtx_);
|
||||
if (tasks_.empty()) return std::nullopt;
|
||||
auto ret = std::move(tasks_.front());
|
||||
tasks_.pop_front();
|
||||
--n_;
|
||||
k.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t size() const noexcept { return n_; }
|
||||
|
||||
protected:
|
||||
std::mutex mtx_;
|
||||
|
||||
private:
|
||||
std::atomic<size_t> n_;
|
||||
|
||||
std::deque<T> tasks_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,102 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class RingBuffer final {
|
||||
public:
|
||||
RingBuffer() = delete;
|
||||
RingBuffer(uint64_t unit, uint64_t bufn) noexcept :
|
||||
buf_(unit*bufn), unit_(unit), bufn_(bufn) {
|
||||
}
|
||||
RingBuffer(const RingBuffer&) = delete;
|
||||
RingBuffer(RingBuffer&&) = default;
|
||||
RingBuffer& operator=(const RingBuffer&) = delete;
|
||||
RingBuffer& operator=(RingBuffer&&) = default;
|
||||
|
||||
template <typename T>
|
||||
uint64_t Mix(uint64_t begin, const T* ptr, uint64_t n) noexcept {
|
||||
assert(unit_ == sizeof(T));
|
||||
|
||||
if (begin < cur_) {
|
||||
const auto drop = cur_ - begin;
|
||||
if (drop >= n) {
|
||||
return cur_;
|
||||
}
|
||||
ptr += drop;
|
||||
n -= drop;
|
||||
begin = cur_;
|
||||
}
|
||||
if (begin > cur_) {
|
||||
const auto skip = begin - cur_;
|
||||
n = std::min(bufn_ - skip, n);
|
||||
}
|
||||
auto buf = reinterpret_cast<T*>(buf_.data());
|
||||
|
||||
const auto [c, r, l] = CalcCursor(begin, n);
|
||||
for (uint64_t i = 0; i < r; ++i) {
|
||||
buf[c+i] += ptr[i];
|
||||
}
|
||||
for (uint64_t i = 0; i < l; ++i) {
|
||||
buf[i] += ptr[r+i];
|
||||
}
|
||||
return begin + n;
|
||||
}
|
||||
void Take(uint8_t* ptr, uint64_t n) noexcept {
|
||||
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||
std::memset(&buf_[c*unit_], 0, r*unit_);
|
||||
std::memset(&buf_[0*unit_], 0, l*unit_);
|
||||
cur_ += n;
|
||||
}
|
||||
|
||||
uint64_t Peek(uint64_t begin, uint8_t* ptr, uint64_t n) noexcept {
|
||||
if (cur_ > bufn_) {
|
||||
const auto actual_begin = std::max(begin, cur_-bufn_);
|
||||
const auto pad = std::min(n, actual_begin - begin);
|
||||
std::memset(ptr, 0, pad*unit_);
|
||||
begin = actual_begin;
|
||||
ptr += pad*unit_;
|
||||
n -= pad;
|
||||
}
|
||||
n = std::min(n, bufn_);
|
||||
|
||||
const auto [c, r, l] = CalcCursor(begin, n);
|
||||
std::memcpy(&ptr[0*unit_], &buf_[c*unit_], r*unit_);
|
||||
std::memcpy(&ptr[r*unit_], &buf_[0*unit_], l*unit_);
|
||||
return begin + n;
|
||||
}
|
||||
void Write(const uint8_t* ptr, uint64_t n) noexcept {
|
||||
const auto [c, r, l] = CalcCursor(cur_, n);
|
||||
std::memcpy(&buf_[c*unit_], &ptr[0*unit_], r*unit_);
|
||||
std::memcpy(&buf_[0*unit_], &ptr[r*unit_], l*unit_);
|
||||
cur_ += n;
|
||||
}
|
||||
|
||||
uint64_t unit() const noexcept { return unit_; }
|
||||
uint64_t bufn() const noexcept { return bufn_; }
|
||||
uint64_t cur() const noexcept { return cur_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buf_;
|
||||
uint64_t unit_;
|
||||
uint64_t bufn_;
|
||||
uint64_t cur_ = 0;
|
||||
|
||||
std::tuple<uint64_t, uint64_t, uint64_t> CalcCursor(
|
||||
uint64_t t, uint64_t n) noexcept {
|
||||
assert(n <= bufn_);
|
||||
const auto c = t % bufn_;
|
||||
const auto r = std::min(bufn_ - c, n);
|
||||
const auto l = n > r? n - r: 0;
|
||||
return {c, r, l};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,114 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/value.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Sequencer : public nf7::File::Interface {
|
||||
public:
|
||||
class Editor;
|
||||
class Session;
|
||||
class Lambda;
|
||||
|
||||
enum Flag : uint8_t {
|
||||
kNone = 0,
|
||||
kCustomItem = 1 << 0, // uses UpdateItem() to draw an item on timeline if enable
|
||||
kParamPanel = 1 << 1,
|
||||
kTooltip = 1 << 2,
|
||||
kMenu = 1 << 3,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
Sequencer() = delete;
|
||||
Sequencer(Flags flags) noexcept : flags_(flags) { }
|
||||
Sequencer(const Sequencer&) = delete;
|
||||
Sequencer(Sequencer&&) = delete;
|
||||
Sequencer& operator=(const Sequencer&) = delete;
|
||||
Sequencer& operator=(Sequencer&&) = delete;
|
||||
|
||||
// Sequencer* is a dummy parameter to avoid issues of multi inheritance.
|
||||
virtual std::shared_ptr<Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Context>&) noexcept = 0;
|
||||
|
||||
virtual void UpdateItem(Editor&) noexcept { }
|
||||
virtual void UpdateParamPanel(Editor&) noexcept { }
|
||||
virtual void UpdateTooltip(Editor&) noexcept { }
|
||||
virtual void UpdateMenu(Editor&) noexcept { }
|
||||
|
||||
Flags flags() const noexcept { return flags_; }
|
||||
|
||||
private:
|
||||
Flags flags_;
|
||||
};
|
||||
|
||||
class Sequencer::Editor {
|
||||
public:
|
||||
Editor() noexcept = default;
|
||||
virtual ~Editor() noexcept = default;
|
||||
Editor(const Editor&) = delete;
|
||||
Editor(Editor&&) = delete;
|
||||
Editor& operator=(const Editor&) = delete;
|
||||
Editor& operator=(Editor&&) = delete;
|
||||
};
|
||||
|
||||
class Sequencer::Session {
|
||||
public:
|
||||
class UnknownNameException final : public nf7::Exception {
|
||||
public:
|
||||
using nf7::Exception::Exception;
|
||||
};
|
||||
|
||||
Session() = default;
|
||||
virtual ~Session() = default;
|
||||
Session(const Session&) = delete;
|
||||
Session(Session&&) = delete;
|
||||
Session& operator=(const Session&) = delete;
|
||||
Session& operator=(Session&&) = delete;
|
||||
|
||||
virtual const nf7::Value* Peek(std::string_view) noexcept = 0;
|
||||
virtual std::optional<nf7::Value> Receive(std::string_view) noexcept = 0;
|
||||
|
||||
const nf7::Value& PeekOrThrow(std::string_view name) {
|
||||
if (auto v = Peek(name)) {
|
||||
return *v;
|
||||
}
|
||||
throw UnknownNameException {std::string {name}+" is unknown"};
|
||||
}
|
||||
nf7::Value ReceiveOrThrow(std::string_view name) {
|
||||
if (auto v = Receive(name)) {
|
||||
return std::move(*v);
|
||||
}
|
||||
throw UnknownNameException {std::string {name}+" is unknown"};
|
||||
}
|
||||
|
||||
virtual void Send(std::string_view, nf7::Value&&) noexcept = 0;
|
||||
|
||||
// thread-safe
|
||||
virtual void Finish() noexcept = 0;
|
||||
};
|
||||
|
||||
class Sequencer::Lambda : public nf7::Context {
|
||||
public:
|
||||
Lambda(nf7::File& f, const std::shared_ptr<Context>& ctx = nullptr) noexcept :
|
||||
Lambda(f.env(), f.id(), ctx) {
|
||||
}
|
||||
Lambda(nf7::Env& env, nf7::File::Id id,
|
||||
const std::shared_ptr<nf7::Context>& ctx = nullptr) noexcept :
|
||||
Context(env, id, ctx) {
|
||||
}
|
||||
|
||||
virtual void Run(const std::shared_ptr<Sequencer::Session>&) noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
@ -1,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
|
@ -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
|
@ -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
|
102
common/thread.hh
102
common/thread.hh
@ -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
|
@ -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
|
@ -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
|
370
common/value.hh
370
common/value.hh
@ -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
|
@ -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
|
@ -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
|
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
ImVec2> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const ImVec2& v) {
|
||||
ar(v.x, v.y);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, ImVec2& v) {
|
||||
ar(v.x, v.y);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ImNodes.h>
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
#include "common/yas_imgui.hh"
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
ImNodes::CanvasState> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const ImNodes::CanvasState& canvas) {
|
||||
ar(canvas.Zoom, canvas.Offset);
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, ImNodes::CanvasState& canvas) {
|
||||
ar(canvas.Zoom, canvas.Offset);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
||||
|
@ -1,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
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F, typename T>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::atomic<T>> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::atomic<T>& v) {
|
||||
ar(v.load());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::atomic<T>& v) {
|
||||
T temp;
|
||||
ar(temp);
|
||||
v.store(temp);
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::filesystem::path> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::filesystem::path& p) {
|
||||
ar(p.generic_string());
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::filesystem::path& p) {
|
||||
std::string str;
|
||||
ar(str);
|
||||
p = std::filesystem::path(str).lexically_normal();
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
std::monostate> {
|
||||
public:
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const std::monostate&) {
|
||||
return ar;
|
||||
}
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, std::monostate&) {
|
||||
return ar;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
30
core/CMakeLists.txt
Normal file
30
core/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
# ---- basic modules
|
||||
set(MODS
|
||||
# ---- interface layer
|
||||
exec
|
||||
|
||||
# ---- implementation layer
|
||||
any
|
||||
lua
|
||||
null
|
||||
sdl2
|
||||
|
||||
# ---- usecase layer
|
||||
init
|
||||
test
|
||||
)
|
||||
|
||||
# ---- core library
|
||||
add_library(nf7core)
|
||||
target_link_libraries(nf7core PRIVATE nf7if)
|
||||
|
||||
foreach(name IN LISTS MODS)
|
||||
add_subdirectory(${name})
|
||||
target_link_libraries(nf7core PRIVATE nf7core_${name})
|
||||
endforeach()
|
||||
|
||||
# ---- generate all.h
|
||||
target_meta_source(nf7core
|
||||
PRIVATE all.c.sh
|
||||
ARGS ${MODS}
|
||||
)
|
36
core/all.c.sh
Executable file
36
core/all.c.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "#include \"core/all.h\""
|
||||
echo
|
||||
echo "#include <assert.h>"
|
||||
echo
|
||||
echo "#include \"util/log.h\""
|
||||
echo
|
||||
echo "const uint32_t NF7CORE_MAX_MODS = UINT32_C($#);"
|
||||
echo
|
||||
echo "uint32_t nf7core_new(struct nf7* nf7, struct nf7_mod** mods) {"
|
||||
echo " assert(nullptr != nf7);"
|
||||
echo " assert(nullptr != mods);"
|
||||
echo
|
||||
echo " nf7->mods.ptr = mods;"
|
||||
echo " nf7->mods.n = 0;"
|
||||
echo
|
||||
for name in $@; do
|
||||
echo " {"
|
||||
echo " extern const struct nf7_mod_meta nf7core_${name};"
|
||||
echo " extern struct nf7_mod* nf7core_${name}_new(const struct nf7*);"
|
||||
echo " nf7util_log_debug(\"loading module: %s\", nf7core_${name}.name);"
|
||||
echo " struct nf7_mod* mod = nf7core_${name}_new(nf7);"
|
||||
echo " if (nullptr != mod) {"
|
||||
echo " assert(nullptr != mod->nf7);"
|
||||
echo " assert(nullptr != mod->meta);"
|
||||
echo " nf7->mods.ptr[nf7->mods.n++] = mod;"
|
||||
echo " nf7util_log_info(\"loaded module: %s\", nf7core_${name}.name);"
|
||||
echo " } else {"
|
||||
echo " nf7util_log_warn(\"failed to load module: %s\", nf7core_${name}.name);"
|
||||
echo " }"
|
||||
echo " }"
|
||||
echo
|
||||
done
|
||||
echo " return nf7->mods.n;"
|
||||
echo "}"
|
14
core/all.h
Normal file
14
core/all.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
|
||||
// A maximum number of modules which nf7core may provide.
|
||||
extern const uint32_t NF7CORE_MAX_MODS;
|
||||
|
||||
|
||||
// Initializes all modules and returns their instances.
|
||||
// sizeof(mods)/sizeof(mods[0]) should be equal to NF7CORE_MAX_MODS
|
||||
uint32_t nf7core_new(struct nf7* nf7, struct nf7_mod** mods);
|
15
core/any/CMakeLists.txt
Normal file
15
core/any/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
add_library(nf7core_any)
|
||||
target_sources(nf7core_any
|
||||
PRIVATE
|
||||
idea.c
|
||||
mod.c
|
||||
)
|
||||
target_link_libraries(nf7core_any
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
|
||||
PUBLIC
|
||||
nf7core_exec
|
||||
)
|
||||
|
103
core/any/idea.c
Normal file
103
core/any/idea.c
Normal file
@ -0,0 +1,103 @@
|
||||
// No copyright
|
||||
#include "core/any/idea.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/malloc.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
|
||||
|
||||
struct nf7core_any_entity {
|
||||
struct nf7core_exec_entity super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
|
||||
struct nf7core_exec_entity* entity;
|
||||
};
|
||||
|
||||
struct nf7core_exec_entity* new_(struct nf7core_exec*);
|
||||
void del_(struct nf7core_exec_entity*);
|
||||
void send_(struct nf7core_exec_entity*, struct nf7util_buffer*);
|
||||
|
||||
|
||||
struct nf7core_exec_entity* new_(struct nf7core_exec* exec) {
|
||||
assert(nullptr != exec);
|
||||
|
||||
struct nf7core_any_entity* this = nf7util_malloc_alloc(exec->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate new entity");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_any_entity) {
|
||||
.super = {
|
||||
.idea = &nf7core_any_idea,
|
||||
.mod = exec,
|
||||
},
|
||||
.malloc = exec->malloc,
|
||||
};
|
||||
return &this->super;
|
||||
}
|
||||
|
||||
void del_(struct nf7core_exec_entity* entity) {
|
||||
struct nf7core_any_entity* this = (void*) entity;
|
||||
if (nullptr != this) {
|
||||
nf7core_exec_entity_del(this->entity);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
void send_(struct nf7core_exec_entity* entity, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != entity);
|
||||
assert(nullptr != buf);
|
||||
|
||||
struct nf7core_any_entity* this = (void*) entity;
|
||||
if (nullptr != this->entity) {
|
||||
nf7core_exec_entity_send(this->entity, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// get name of the requested idea
|
||||
const uint8_t* name = buf->array.ptr;
|
||||
const uint64_t namelen = buf->array.n;
|
||||
if (0U == namelen) {
|
||||
nf7util_log_warn("expected an idea name, but got an empty string");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
// find the requested idea and create new entity
|
||||
this->entity = nf7core_exec_entity_new(this->super.mod, name, namelen);
|
||||
|
||||
// assign the return value
|
||||
struct nf7util_buffer* result = nullptr;
|
||||
if (nullptr != this->entity) {
|
||||
result = nf7util_buffer_new_from_cstr(this->malloc, "");
|
||||
nf7util_log_debug("sub-entity is created: %.*s", (int) namelen, name);
|
||||
} else {
|
||||
result = nf7util_buffer_new_from_cstr(this->malloc, "FAIL");
|
||||
nf7util_log_warn("unknown idea requested: %.*s", (int) namelen, name);
|
||||
}
|
||||
|
||||
// return the result
|
||||
if (nullptr == result) {
|
||||
nf7util_log_error("failed to allocate a buffer to return result");
|
||||
goto EXIT;
|
||||
}
|
||||
nf7core_exec_entity_recv(&this->super, result);
|
||||
|
||||
EXIT:
|
||||
nf7util_buffer_unref(buf);
|
||||
}
|
||||
|
||||
|
||||
const struct nf7core_exec_idea nf7core_any_idea = {
|
||||
.name = (const uint8_t*) "nf7core_any",
|
||||
.details = (const uint8_t*) "creates and wraps other entity of an idea chosen at runtime",
|
||||
.mod = &nf7core_any,
|
||||
|
||||
.new = new_,
|
||||
.del = del_,
|
||||
.send = send_,
|
||||
};
|
7
core/any/idea.h
Normal file
7
core/any/idea.h
Normal file
@ -0,0 +1,7 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "core/any/mod.h"
|
||||
|
||||
|
||||
extern const struct nf7core_exec_idea nf7core_any_idea;
|
66
core/any/mod.c
Normal file
66
core/any/mod.c
Normal file
@ -0,0 +1,66 @@
|
||||
// No copyright
|
||||
#include "core/any/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/any/idea.h"
|
||||
#include "core/exec/idea.h"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
|
||||
|
||||
struct nf7_mod* nf7core_any_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
// find nf7core_exec
|
||||
struct nf7core_exec* exec = (void*) nf7_get_mod_by_meta(nf7, &nf7core_exec);
|
||||
if (nullptr == exec) {
|
||||
nf7util_log_error("not found nf7core_exec, nf7core_any is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// create module data
|
||||
struct nf7core_any* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_any) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_any,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.exec = exec,
|
||||
};
|
||||
|
||||
// register idea
|
||||
if (!nf7core_exec_idea_register(exec, &nf7core_any_idea)) {
|
||||
nf7util_log_error("failed to register an idea, nf7core_any");
|
||||
goto ABORT;
|
||||
}
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
del_(&this->super);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_any* this = (void*) mod;
|
||||
if (nullptr != this) {
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_any = {
|
||||
.name = (const uint8_t*) "nf7core_any",
|
||||
.desc = (const uint8_t*) "executes any things",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
28
core/any/mod.h
Normal file
28
core/any/mod.h
Normal file
@ -0,0 +1,28 @@
|
||||
// No copyright
|
||||
//
|
||||
// This module provides an idea, nf7core_any. Its entity can create and wrap
|
||||
// other entity of any idea chosen at runtime.
|
||||
//
|
||||
// An entity of idea, nf7core_any, is composed of 2 states:
|
||||
// INIT state:
|
||||
// The entity is in this state right after the born. Accepts a string that
|
||||
// expressing idea name and returns a string. If the returned string is
|
||||
// empty, transitions to PIPE state with an sub-entity of the specified idea,
|
||||
// otherwise error.
|
||||
// PIPE state:
|
||||
// All buffers from the client is passed to the sub-entity, and from the
|
||||
// sub-entity is to the cleint.
|
||||
#pragma once
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_any {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
struct nf7core_exec* exec;
|
||||
};
|
||||
extern const struct nf7_mod_meta nf7core_any;
|
14
core/exec/CMakeLists.txt
Normal file
14
core/exec/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
add_library(nf7core_exec)
|
||||
target_sources(nf7core_exec
|
||||
PRIVATE
|
||||
mod.c
|
||||
PUBLIC
|
||||
entity.h
|
||||
idea.h
|
||||
mod.h
|
||||
)
|
||||
target_link_libraries(nf7core_exec
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
)
|
74
core/exec/entity.h
Normal file
74
core/exec/entity.h
Normal file
@ -0,0 +1,74 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/exec/idea.h"
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_exec_entity {
|
||||
const struct nf7core_exec_idea* idea;
|
||||
struct nf7core_exec* mod;
|
||||
|
||||
void* data;
|
||||
void (*on_recv)(
|
||||
struct nf7core_exec_entity*,
|
||||
struct nf7util_buffer*);
|
||||
};
|
||||
|
||||
|
||||
static inline struct nf7core_exec_entity* nf7core_exec_entity_new(
|
||||
struct nf7core_exec* mod, const uint8_t* name, size_t namelen) {
|
||||
const struct nf7core_exec_idea* idea =
|
||||
nf7core_exec_idea_find(mod, name, namelen);
|
||||
if (nullptr == idea) {
|
||||
nf7util_log_error("missing idea: %.*s", (int) namelen, name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct nf7core_exec_entity* entity = idea->new(mod);
|
||||
if (nullptr == entity) {
|
||||
nf7util_log_error("failed to create entity of '%.*s'", (int) namelen, name);
|
||||
return nullptr;
|
||||
}
|
||||
assert(idea == entity->idea);
|
||||
assert(mod == entity->mod);
|
||||
return entity;
|
||||
}
|
||||
|
||||
// The implementation of entity use this to send buffer to the client.
|
||||
// Takes ownership of buf.
|
||||
static inline void nf7core_exec_entity_recv(struct nf7core_exec_entity* this, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != buf);
|
||||
|
||||
if (nullptr != this->on_recv) {
|
||||
this->on_recv(this, buf);
|
||||
} else {
|
||||
nf7util_buffer_unref(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// The client of entity use this to send buffer to the implementation.
|
||||
// Takes ownership of buf.
|
||||
static inline void nf7core_exec_entity_send(
|
||||
struct nf7core_exec_entity* this, struct nf7util_buffer* buf) {
|
||||
assert(nullptr != this);
|
||||
assert(nullptr != this->idea);
|
||||
assert(nullptr != this->idea->send);
|
||||
assert(nullptr != buf);
|
||||
|
||||
this->idea->send(this, buf);
|
||||
}
|
||||
|
||||
static inline void nf7core_exec_entity_del(struct nf7core_exec_entity* this) {
|
||||
if (nullptr != this) {
|
||||
assert(nullptr != this->idea);
|
||||
assert(nullptr != this->idea->del);
|
||||
|
||||
this->idea->del(this);
|
||||
}
|
||||
}
|
48
core/exec/idea.h
Normal file
48
core/exec/idea.h
Normal file
@ -0,0 +1,48 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util/buffer.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_exec_idea {
|
||||
const uint8_t* name;
|
||||
const uint8_t* details;
|
||||
const struct nf7_mod_meta* mod;
|
||||
|
||||
struct nf7core_exec_entity* (*new)(struct nf7core_exec*);
|
||||
void (*del)(struct nf7core_exec_entity*);
|
||||
|
||||
void (*send)(
|
||||
struct nf7core_exec_entity*,
|
||||
struct nf7util_buffer*);
|
||||
};
|
||||
|
||||
|
||||
static inline bool nf7core_exec_idea_register(
|
||||
struct nf7core_exec* mod, const struct nf7core_exec_idea* idea) {
|
||||
assert(nullptr != mod);
|
||||
assert(nullptr != idea);
|
||||
|
||||
return nf7core_exec_ideas_insert(&mod->ideas, UINT64_MAX, idea);
|
||||
}
|
||||
|
||||
static inline const struct nf7core_exec_idea* nf7core_exec_idea_find(
|
||||
const struct nf7core_exec* mod, const uint8_t* name, size_t namelen) {
|
||||
assert(nullptr != mod);
|
||||
assert(nullptr != name || 0U == namelen);
|
||||
|
||||
for (uint32_t i = 0; i < mod->ideas.n; ++i) {
|
||||
const struct nf7core_exec_idea* idea = mod->ideas.ptr[i];
|
||||
if (nf7util_str_equal_cstr(name, namelen, idea->name)) {
|
||||
return idea;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
50
core/exec/mod.c
Normal file
50
core/exec/mod.c
Normal file
@ -0,0 +1,50 @@
|
||||
// No copyright
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
|
||||
|
||||
struct nf7_mod* nf7core_exec_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_exec* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
goto ABORT;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_exec) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_exec,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
};
|
||||
|
||||
nf7core_exec_ideas_init(&this->ideas, this->malloc);
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting module init");
|
||||
del_((struct nf7_mod*) this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_exec* this = (void*) mod;
|
||||
nf7core_exec_ideas_deinit(&this->ideas);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_exec = {
|
||||
.name = (const uint8_t*) "nf7core_exec",
|
||||
.desc = (const uint8_t*) "provides a registry for executables",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
25
core/exec/mod.h
Normal file
25
core/exec/mod.h
Normal file
@ -0,0 +1,25 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/array.h"
|
||||
#include "util/malloc.h"
|
||||
|
||||
|
||||
struct nf7core_exec_idea;
|
||||
struct nf7core_exec_entity;
|
||||
|
||||
NF7UTIL_ARRAY_INLINE(nf7core_exec_ideas, const struct nf7core_exec_idea*);
|
||||
|
||||
|
||||
struct nf7core_exec {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7* nf7;
|
||||
struct nf7util_malloc* malloc;
|
||||
|
||||
struct nf7core_exec_ideas ideas;
|
||||
};
|
||||
|
||||
extern const struct nf7_mod_meta nf7core_exec;
|
12
core/init/CMakeLists.txt
Normal file
12
core/init/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_library(nf7core_init)
|
||||
target_sources(nf7core_init
|
||||
PRIVATE
|
||||
mod.c
|
||||
factory.priv.h
|
||||
)
|
||||
target_link_libraries(nf7core_init
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
)
|
||||
|
95
core/init/factory.priv.h
Normal file
95
core/init/factory.priv.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
#include "core/exec/mod.h"
|
||||
|
||||
|
||||
struct nf7core_init_factory {
|
||||
struct nf7* nf7;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
uv_idle_t idle;
|
||||
|
||||
const uint8_t* idea_name;
|
||||
size_t idea_namelen;
|
||||
|
||||
void* data;
|
||||
void (*on_created)(struct nf7core_init_factory*, struct nf7core_exec_entity*);
|
||||
// after the callback is called
|
||||
};
|
||||
|
||||
static struct nf7core_init_factory* factory_new_(struct nf7*, const uint8_t*, size_t);
|
||||
static void factory_del_(struct nf7core_init_factory*);
|
||||
static void factory_on_idle_(uv_idle_t*);
|
||||
static void factory_on_close_(uv_handle_t*);
|
||||
|
||||
|
||||
static inline struct nf7core_init_factory* factory_new_(
|
||||
struct nf7* nf7, const uint8_t* idea_name, size_t idea_namelen) {
|
||||
struct nf7core_init_factory* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate the first factory");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_init_factory) {
|
||||
.nf7 = nf7,
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
.idea_name = idea_name,
|
||||
.idea_namelen = idea_namelen,
|
||||
};
|
||||
|
||||
uv_idle_init(this->uv, &this->idle);
|
||||
this->idle.data = this;
|
||||
|
||||
uv_idle_start(&this->idle, factory_on_idle_);
|
||||
return this;
|
||||
}
|
||||
|
||||
static inline void factory_del_(struct nf7core_init_factory* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
if (nullptr != this->idle.data) {
|
||||
uv_idle_stop(&this->idle);
|
||||
uv_close((uv_handle_t*) &this->idle, factory_on_close_);
|
||||
return;
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static inline void factory_on_idle_(uv_idle_t* idle) {
|
||||
assert(nullptr != idle);
|
||||
struct nf7core_init_factory* this = idle->data;
|
||||
|
||||
struct nf7core_exec* exec = (void*) nf7_get_mod_by_meta(this->nf7, &nf7core_exec);
|
||||
if (nullptr == exec) {
|
||||
nf7util_log_error("nf7core_exec module is not installed");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
struct nf7core_exec_entity* entity = nf7core_exec_entity_new(exec, this->idea_name, this->idea_namelen);
|
||||
if (nullptr == entity) {
|
||||
nf7util_log_error("failed to create new entity");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
assert(nullptr != this->on_created);
|
||||
this->on_created(this, entity);
|
||||
|
||||
EXIT:
|
||||
uv_idle_stop(&this->idle);
|
||||
}
|
||||
static inline void factory_on_close_(uv_handle_t* handle) {
|
||||
assert(nullptr != handle);
|
||||
struct nf7core_init_factory* this = handle->data;
|
||||
handle->data = nullptr;
|
||||
factory_del_(this);
|
||||
}
|
97
core/init/mod.c
Normal file
97
core/init/mod.c
Normal file
@ -0,0 +1,97 @@
|
||||
// No copyright
|
||||
#include "core/init/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/exec/mod.h"
|
||||
#include "core/init/factory.priv.h"
|
||||
|
||||
|
||||
#define IDEA_NAME "nf7core_any"
|
||||
|
||||
|
||||
static void del_(struct nf7_mod*);
|
||||
static void start_(struct nf7core_init_factory*, struct nf7core_exec_entity*);
|
||||
|
||||
struct nf7_mod* nf7core_init_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_init* this = nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate module context");
|
||||
goto ABORT;
|
||||
}
|
||||
|
||||
*this = (struct nf7core_init) {
|
||||
.super = {
|
||||
.meta = &nf7core_init,
|
||||
.nf7 = nf7,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
};
|
||||
|
||||
this->factory = factory_new_(nf7, (const uint8_t*) IDEA_NAME, sizeof(IDEA_NAME)-1);
|
||||
if (nullptr == this->factory) {
|
||||
nf7util_log_error("failed to start the first factory");
|
||||
goto ABORT;
|
||||
}
|
||||
this->factory->data = this;
|
||||
this->factory->on_created = start_;
|
||||
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
del_(&this->super);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7_mod* mod) {
|
||||
struct nf7core_init* this = (void*) mod;
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
nf7util_log_info("delete factory");
|
||||
factory_del_(this->factory);
|
||||
this->factory = nullptr;
|
||||
nf7core_exec_entity_del(this->entity);
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void start_(struct nf7core_init_factory* factory, struct nf7core_exec_entity* entity) {
|
||||
assert(nullptr != factory);
|
||||
struct nf7core_init* this = factory->data;
|
||||
assert(nullptr != this);
|
||||
|
||||
this->entity = entity;
|
||||
if (nullptr == this->entity) {
|
||||
nf7util_log_warn("failed to create new entity");
|
||||
goto EXIT;
|
||||
}
|
||||
|
||||
struct nf7util_buffer* buf = nf7util_buffer_new(this->malloc, 0);
|
||||
if (nullptr == buf) {
|
||||
nf7util_log_error("failed to allocate an empty buffer to send as the first trigger");
|
||||
goto EXIT;
|
||||
}
|
||||
nf7core_exec_entity_send(this->entity, buf);
|
||||
|
||||
EXIT:
|
||||
this->factory = nullptr;
|
||||
factory_del_(factory);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_init = {
|
||||
.name = (const uint8_t*) "nf7core_init",
|
||||
.desc = (const uint8_t*) "creates the first entity",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_,
|
||||
};
|
22
core/init/mod.h
Normal file
22
core/init/mod.h
Normal file
@ -0,0 +1,22 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "core/exec/entity.h"
|
||||
|
||||
|
||||
struct nf7core_init_factory;
|
||||
|
||||
struct nf7core_init {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
|
||||
struct nf7core_init_factory* factory;
|
||||
struct nf7core_exec_entity* entity;
|
||||
};
|
||||
extern const struct nf7_mod_meta nf7core_init;
|
21
core/lua/CMakeLists.txt
Normal file
21
core/lua/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
add_library(nf7core_lua)
|
||||
target_sources(nf7core_lua
|
||||
PRIVATE
|
||||
mod.c
|
||||
thread.c
|
||||
value_ptr.c
|
||||
PUBLIC
|
||||
mod.h
|
||||
thread.h
|
||||
value.h
|
||||
value_ptr.h
|
||||
)
|
||||
target_link_libraries(nf7core_lua
|
||||
PRIVATE
|
||||
nf7if
|
||||
nf7util
|
||||
luajit
|
||||
)
|
||||
target_tests(nf7core_lua
|
||||
thread.test.c
|
||||
)
|
66
core/lua/mod.c
Normal file
66
core/lua/mod.c
Normal file
@ -0,0 +1,66 @@
|
||||
// No copyright
|
||||
#include "core/lua/mod.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
|
||||
static void del_(struct nf7core_lua*);
|
||||
|
||||
struct nf7_mod* nf7core_lua_new(struct nf7* nf7) {
|
||||
assert(nullptr != nf7);
|
||||
|
||||
struct nf7core_lua* this =
|
||||
nf7util_malloc_alloc(nf7->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate a module context");
|
||||
goto ABORT;
|
||||
}
|
||||
*this = (struct nf7core_lua) {
|
||||
.super = {
|
||||
.nf7 = nf7,
|
||||
.meta = &nf7core_lua,
|
||||
},
|
||||
.malloc = nf7->malloc,
|
||||
.uv = nf7->uv,
|
||||
};
|
||||
|
||||
this->thread = nf7core_lua_thread_new(this, nullptr, nullptr);
|
||||
if (nullptr == this->thread) {
|
||||
nf7util_log_error("failed to create main thread");
|
||||
goto ABORT;
|
||||
}
|
||||
return &this->super;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting lua module init");
|
||||
del_(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_lua* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullptr != this->thread) {
|
||||
nf7core_lua_thread_unref(this->thread);
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void del_mod_(struct nf7_mod* mod) {
|
||||
struct nf7core_lua* this = (void*) mod;
|
||||
del_(this);
|
||||
}
|
||||
|
||||
const struct nf7_mod_meta nf7core_lua = {
|
||||
.name = (const uint8_t*) "nf7core_lua",
|
||||
.desc = (const uint8_t*) "lua script execution",
|
||||
.ver = NF7_VERSION,
|
||||
|
||||
.del = del_mod_,
|
||||
};
|
25
core/lua/mod.h
Normal file
25
core/lua/mod.h
Normal file
@ -0,0 +1,25 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lua.h>
|
||||
|
||||
#include "nf7.h"
|
||||
|
||||
#include "util/malloc.h"
|
||||
#include "util/refcnt.h"
|
||||
|
||||
|
||||
extern const struct nf7_mod_meta nf7core_lua;
|
||||
|
||||
struct nf7core_lua {
|
||||
struct nf7_mod super;
|
||||
|
||||
struct nf7util_malloc* malloc;
|
||||
uv_loop_t* uv;
|
||||
|
||||
struct nf7core_lua_thread* thread;
|
||||
};
|
||||
|
||||
struct nf7_mod* nf7core_lua_new(struct nf7*);
|
180
core/lua/thread.c
Normal file
180
core/lua/thread.c
Normal file
@ -0,0 +1,180 @@
|
||||
// No copyright
|
||||
#include "core/lua/thread.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
static void* alloc_(void*, void*, size_t, size_t);
|
||||
static void del_(struct nf7core_lua_thread*);
|
||||
|
||||
static void on_time_(uv_timer_t*);
|
||||
static void on_close_(uv_handle_t*);
|
||||
|
||||
NF7UTIL_REFCNT_IMPL(, nf7core_lua_thread, {del_(this);});
|
||||
|
||||
struct nf7core_lua_thread* nf7core_lua_thread_new(
|
||||
struct nf7core_lua* mod,
|
||||
struct nf7core_lua_thread* base,
|
||||
struct nf7core_lua_value_ptr* func) {
|
||||
assert(nullptr != mod);
|
||||
|
||||
struct nf7core_lua_thread* this =
|
||||
nf7util_malloc_alloc(mod->malloc, sizeof(*this));
|
||||
if (nullptr == this) {
|
||||
nf7util_log_error("failed to allocate new thread context");
|
||||
return nullptr;
|
||||
}
|
||||
*this = (struct nf7core_lua_thread) {
|
||||
.mod = mod,
|
||||
.malloc = mod->malloc,
|
||||
.uv = mod->uv,
|
||||
};
|
||||
|
||||
if (0 != nf7util_log_uv(uv_timer_init(this->uv, &this->timer))) {
|
||||
nf7util_log_error("failed to init uv timer");
|
||||
goto ABORT;
|
||||
}
|
||||
this->timer.data = this;
|
||||
|
||||
if (nullptr != base) {
|
||||
this->lua = lua_newthread(base->lua);
|
||||
if (nullptr == this->lua) {
|
||||
nf7util_log_error("failed to allocate new lua thread");
|
||||
goto ABORT;
|
||||
}
|
||||
this->base = base;
|
||||
nf7core_lua_thread_ref(this->base);
|
||||
} else {
|
||||
this->lua_owned = true;
|
||||
this->lua = lua_newstate(alloc_, this);
|
||||
if (nullptr == this->lua) {
|
||||
nf7util_log_error("failed to allocate new lua state");
|
||||
goto ABORT;
|
||||
}
|
||||
nf7util_log_debug("new lua state is created");
|
||||
}
|
||||
|
||||
if (nullptr != func) {
|
||||
this->state = NF7CORE_LUA_THREAD_PAUSED;
|
||||
nf7core_lua_value_ptr_push(func, this->lua);
|
||||
} else {
|
||||
this->state = NF7CORE_LUA_THREAD_DONE;
|
||||
}
|
||||
|
||||
nf7core_lua_thread_ref(this);
|
||||
return this;
|
||||
|
||||
ABORT:
|
||||
nf7util_log_warn("aborting thread creation");
|
||||
del_(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nf7core_lua_thread_resume_varg_after(
|
||||
struct nf7core_lua_thread* this, uint64_t timeout, va_list vargs) {
|
||||
assert(nullptr != this);
|
||||
assert(NF7CORE_LUA_THREAD_PAUSED == this->state);
|
||||
|
||||
// stores parameters
|
||||
for (uint32_t i = 0;; ++i) {
|
||||
struct nf7core_lua_value* src = va_arg(vargs, struct nf7core_lua_value*);
|
||||
if (nullptr == src) {
|
||||
this->args.n = i;
|
||||
break;
|
||||
}
|
||||
assert(i < NF7CORE_LUA_THREAD_MAX_ARGS &&
|
||||
"too many args or forgotten last nullptr");
|
||||
|
||||
struct nf7core_lua_value* dst = &this->args.ptr[i];
|
||||
if (!nf7core_lua_value_set(dst, src)) {
|
||||
nf7core_lua_value_set(dst, &NF7CORE_LUA_VALUE_NIL());
|
||||
nf7util_log_warn(
|
||||
"failed to store parameter value, it's replaced by nil");
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != nf7util_log_uv(uv_timer_start(&this->timer, on_time_, timeout, 0))) {
|
||||
nf7util_log_error(
|
||||
"failed to start timer for resuming thread");
|
||||
return false;
|
||||
}
|
||||
nf7core_lua_thread_ref(this);
|
||||
|
||||
nf7util_log_debug("lua thread state change: PAUSED -> SCHEDULED");
|
||||
this->state = NF7CORE_LUA_THREAD_SCHEDULED;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* alloc_(void* data, void* ptr, size_t, size_t nsize) {
|
||||
struct nf7core_lua_thread* this = data;
|
||||
return nf7util_malloc_realloc(this->malloc, ptr, nsize);
|
||||
}
|
||||
|
||||
static void del_(struct nf7core_lua_thread* this) {
|
||||
if (nullptr == this) {
|
||||
return;
|
||||
}
|
||||
if (nullptr != this->timer.data) {
|
||||
uv_close((uv_handle_t*) &this->timer, on_close_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullptr != this->lua && this->lua_owned) {
|
||||
lua_close(this->lua);
|
||||
nf7util_log_debug("lua state is closed");
|
||||
}
|
||||
if (nullptr != this->base) {
|
||||
nf7core_lua_thread_unref(this->base);
|
||||
}
|
||||
nf7util_malloc_free(this->malloc, this);
|
||||
}
|
||||
|
||||
static void on_time_(uv_timer_t* timer) {
|
||||
struct nf7core_lua_thread* this = timer->data;
|
||||
assert(nullptr != this);
|
||||
assert(NF7CORE_LUA_THREAD_SCHEDULED == this->state);
|
||||
|
||||
lua_State* L = this->lua;
|
||||
for (uint32_t i = 0; i < this->args.n; ++i) {
|
||||
struct nf7core_lua_value* v = &this->args.ptr[i];
|
||||
nf7core_lua_value_push(v, L);
|
||||
nf7core_lua_value_unset(v);
|
||||
}
|
||||
|
||||
nf7util_log_debug("lua thread state change: SCHEDULED -> RUNNING");
|
||||
this->state = NF7CORE_LUA_THREAD_RUNNING;
|
||||
|
||||
const int result = lua_resume(L, (int) this->args.n);
|
||||
switch (result) {
|
||||
case 0:
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> DONE");
|
||||
this->state = NF7CORE_LUA_THREAD_DONE;
|
||||
break;
|
||||
case LUA_YIELD:
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> PAUSED");
|
||||
this->state = NF7CORE_LUA_THREAD_PAUSED;
|
||||
break;
|
||||
case LUA_ERRMEM:
|
||||
case LUA_ERRRUN:
|
||||
case LUA_ERRERR:
|
||||
nf7util_log_warn("lua execution failed: ", lua_tostring(L, -1));
|
||||
nf7util_log_debug("lua thread state change: RUNNING -> ABORTED");
|
||||
this->state = NF7CORE_LUA_THREAD_ABORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (nullptr != this->post_exec) {
|
||||
this->post_exec(this, L);
|
||||
}
|
||||
lua_settop(L, 0);
|
||||
|
||||
nf7core_lua_thread_unref(this);
|
||||
}
|
||||
|
||||
static void on_close_(uv_handle_t* handle) {
|
||||
struct nf7core_lua_thread* this = handle->data;
|
||||
this->timer.data = nullptr;
|
||||
del_(this);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user