212 Commits

Author SHA1 Message Date
fe7531260f fix simultaneous use of GL context 2022-11-03 21:53:24 +09:00
8e571ee446 implement blit of GL/Framebuffer 2022-11-03 11:54:59 +09:00
42bc1da204 add an assertion for checking OpenGL errors after each tasks 2022-11-03 11:54:23 +09:00
f0c4f893bd fix an issue of a possibility of race condition in unlocking mutex 2022-11-03 11:53:32 +09:00
8f6ff99136 improve ugly codes in locking OpenGL objects 2022-11-03 10:59:28 +09:00
5e515e23fa improve an interface of nf7::Value 2022-11-02 22:54:21 +09:00
3b25790f3c fix an issue that GL/Shader ignores included shader nfile updates 2022-11-02 21:53:22 +09:00
5e8fa70805 fix compiler errors on MSVC 2022-11-01 12:03:00 -07:00
b553f44f95 fix tiny bugs in draw action of GL/Program 2022-10-31 13:12:50 +09:00
08fcda65ca fix an issue that depth config is not applied to OpenGL program 2022-10-31 13:12:16 +09:00
4de72c20af implement Texture Viewer for GL/Texture 2022-10-31 12:18:27 +09:00
2a2c8f3109 fix an issue that unfinalized future might be refered 2022-10-29 22:55:32 +09:00
20b5217869 add string manipulation library to std table in LuaJIT 2022-10-29 22:54:43 +09:00
0cb8468a58 support drawing with depth test in GL/Program 2022-10-29 11:56:55 +09:00
fa1a29c325 support depth/stencil buffer 2022-10-29 11:50:42 +09:00
9fc39b986a (WIP) improve GL/Framebuffer to use depth/stencil buffers 2022-10-29 01:15:10 +09:00
e607a587c1 fix invalid memory reference 2022-10-29 01:14:15 +09:00
31bbf118e1 improve nf7::AggregatePromise not to ignore errors from children 2022-10-29 01:13:51 +09:00
8ef4abd75e refactor gl::Obj 2022-10-29 00:00:43 +09:00
694e9e34bb improve nf7::NFileWatcher to watch multiple native paths 2022-10-27 23:53:40 +09:00
c1f7328628 implement preprocessor for GLSL 2022-10-27 23:53:22 +09:00
dc3d8b15bf add GLSL version specifier automatically if not provided 2022-10-27 23:24:16 +09:00
05201ef13e enhance nf7::Future::Chain 2022-10-27 23:24:16 +09:00
1b424e299c implement automatic-cache-dropping of GL objects 2022-10-26 10:44:19 +09:00
3c09ac4491 enhance logging in OpenGL actions 2022-10-26 10:38:17 +09:00
1ee59aaedf improve usage of opengl PBO and fence 2022-10-26 09:42:02 +09:00
42738f4923 implement drawing with texture in GL/Program 2022-10-25 23:26:47 +09:00
af19f7807d fix behavior of download command in GL/Texture 2022-10-25 17:58:22 +09:00
9dada90b78 support GL_ELEMENT_ARRAY_BUFFER 2022-10-25 17:58:22 +09:00
419b9a98e2 commonize codes to lock GL objects using template 2022-10-25 16:34:16 +09:00
d5b5e664d7 use custom enum to represent metadata of GL objects 2022-10-25 11:43:58 +09:00
f869f191f2 split GLenum utility to other file 2022-10-25 11:06:18 +09:00
c42b63da0c add offset parameters in uploading texture through GL/Texture 2022-10-24 09:04:21 +09:00
1e33062e9a implement dowloading GL/Texture 2022-10-22 16:52:42 +09:00
ecb7fca1e8 support uniform variables in GL/Program 2022-10-21 12:50:09 +09:00
50f270b571 improve nf7::Value interface 2022-10-21 12:49:48 +09:00
e927179176 fix deadlock when failed to fetch VAO or FBO while GL/Program drawing 2022-10-21 11:09:18 +09:00
ffe3d6265d fix an recursion issue in nf7::luajit::ToValue() 2022-10-21 11:05:43 +09:00
3d14e709b1 add nf7::Value::integerOrScalar() accessor 2022-10-21 11:04:12 +09:00
a19b7636fa fix CMake CMP0135 issues
3b0d86c0a5 wasn't worked actually :(
2022-10-21 09:56:20 +09:00
c59d3e2f29 support GL/Program instanced drawing 2022-10-20 13:31:13 +09:00
7f90b40236 allow user to specify viewport before GL/Program drawing 2022-10-20 13:22:10 +09:00
396bebf6b5 implement clearing framebuffer 2022-10-20 13:09:39 +09:00
3b0d86c0a5 fix an issue of configuration on older CMake 2022-10-20 13:09:20 +09:00
fcf3dad206 implement draw command of GL/Program 2022-10-19 19:21:52 +09:00
9a637586e2 add new sugar syntax for nf7::Future::Chain 2022-10-19 19:21:03 +09:00
ca5dfb5933 add GL/Framebuffer 2022-10-19 12:25:21 +09:00
38fc3b680a improve GL/Texture to allocate image on initialization 2022-10-19 12:25:09 +09:00
96fd71df07 separate initialization codes of GL objects 2022-10-19 01:06:47 +09:00
0a55250f52 add GL/VertexArray 2022-10-17 17:49:31 +09:00
2c6608ea09 enhance nf7::Future::Then() method to allow caller to choose an executor 2022-10-15 10:29:35 +09:00
451094c9fc add GL/Program 2022-10-15 08:19:07 +09:00
94615b3669 add nf7::AggregatePromise 2022-10-15 08:14:58 +09:00
4e83f7b5e9 add GL/Shader 2022-10-12 10:25:19 +09:00
7ee770e67d add nf7::Future::Chain() method 2022-10-12 10:25:10 +09:00
4b79c5e4df add GL/Texture 2022-10-10 13:03:22 +09:00
58d39739e8 add GL/Buffer 2022-10-10 11:18:34 +09:00
77dc8cef32 improve nf7::Mutex 2022-10-09 17:59:13 +09:00
2edd7d9e88 improve Node interface 2022-10-07 11:53:36 +09:00
cafba96971 improve synchronization in main loop 2022-10-07 11:53:36 +09:00
e3dbcb016d unify LuaJIT/Node and LuaJIT/InlineNode into LuaJIT/Node
add std.import to LuaJIT std lib
2022-10-01 11:10:30 +09:00
4a25b88e25 add GL task executor to Env 2022-09-30 21:47:06 +09:00
5c84d95139 improve an NodeRootLambda interface on LuaJIT 2022-09-30 17:52:32 +09:00
f96188ef14 rename NodeRootSelectLambda -> NodeRootLambda
and improve common/luajit.hh
2022-09-30 17:18:46 +09:00
7d696cfbd9 remove unused headers 2022-09-30 16:29:15 +09:00
615d2eacb0 fix an issue that tasks pushed to Audio/Context aren't run when nf7 is shutting down 2022-09-28 13:46:45 +09:00
c2c4b83918 improve main.cc readability 2022-09-28 13:28:27 +09:00
4f13dd9456 refactor Audio/Device 2022-09-27 11:09:45 +09:00
7b2f9c8d55 improve device list of Audio/Context 2022-09-26 23:32:34 +09:00
b949383932 fix an issue that node sockets can be duplicated 2022-09-26 12:45:05 +09:00
5ef347fa2e fix an issue that global table is not applied in lua's main thread 2022-09-26 12:31:06 +09:00
f77a60831c refactor LuaJIT/InlineNode 2022-09-26 12:18:49 +09:00
8688ef98b6 add nf7::gui::Config 2022-09-26 12:00:46 +09:00
d0d6a2ebd5 refactor LuaJIT/Node 2022-09-26 12:00:46 +09:00
ab802d02e3 improve nf7::Future for using with const qulifier 2022-09-26 12:00:46 +09:00
007882ccfd fix data race 2022-09-26 02:15:13 +09:00
2082a6e482 enhance Future::Then() 2022-09-26 02:15:13 +09:00
8ffad3347f rename NativeFile -> NFile 2022-09-26 02:15:13 +09:00
7275e9a710 add NFileWatcher 2022-09-26 02:15:13 +09:00
46e6a78682 allow GenericMemento to access data through arrow operator
the arrow allows
2022-09-26 02:15:13 +09:00
09375ced9c restore nf7::Task once removed 2022-09-26 02:15:13 +09:00
79f3cc9639 add bitmanip functions to Lua script std table 2022-09-24 13:46:37 +09:00
f16937da5a update Lua std library 2022-09-23 10:33:02 +09:00
7dbda8d281 add Value/Plot 2022-09-23 09:36:16 +09:00
8879e9ed41 update a thirdparty lib, ImPlot 2022-09-23 09:36:16 +09:00
e045e86b11 add nf7::gui::ConfigPopup 2022-09-22 17:22:45 +09:00
07b198f71e generalize nf7::util::Uniq 2022-09-22 17:22:45 +09:00
b004723464 add new thirdparty library, yaml-cpp 2022-09-22 17:22:45 +09:00
6e820daef8 add new thirdparty library, magic_enum
this is awesome X)
2022-09-20 22:27:00 +09:00
dcbd3594cf add EnumSerializer 2022-09-19 10:04:30 +09:00
c5590092fa fix synchronization issues 2022-09-15 19:36:21 +09:00
8339cc814a enable sanitizers on g++ 2022-09-15 19:36:00 +09:00
bbfee304bd add 'perform GC cycle' option to LuaJIT/Context menu 2022-09-14 15:38:08 +09:00
2040898bd7 fix an issue that items are not kept sorted in layers of Sequencer/Timeline 2022-09-14 15:25:47 +09:00
46ddb16128 implement exception handling in some complicated cases 2022-09-14 15:25:14 +09:00
d6a9c62a63 fix an issue that NodeRootSelectLambda violates assetion 2022-09-02 17:42:33 +09:00
a5f3e459bf enhance locking feature of System/NativeFile 2022-09-02 17:42:33 +09:00
336f436942 add Mutex 2022-09-02 17:42:33 +09:00
589cd4b4fc add NodeRootSelectLambda and remove luajit::Thread::Lambda 2022-08-31 15:04:49 +09:00
5505488661 fix unnatural English 2022-08-31 13:10:28 +09:00
f76960da1b fix compile errors on release build of g++ 2022-08-30 12:48:32 +09:00
6a4ddb1642 fix memory leaks, and reference of uninitialized value 2022-08-28 19:45:12 +09:00
feb481f00f fix FileHolder issues 2022-08-28 18:31:44 +09:00
69e3c0dcda fix Value/Curve UI 2022-08-28 17:22:08 +09:00
6ad8eb6230 fix issues in LuaJIT builtin library 2022-08-28 16:12:57 +09:00
1b6b2435ed add nf7::DirItem flag to LuaJIT/InlineNode 2022-08-28 15:20:38 +09:00
ea070399fc fix an issue that mouse cursor doesn't change from arrow 2022-08-28 15:06:25 +09:00
df2daddce1 fix an issue that timeline cursor's header is clipped out 2022-08-28 15:05:47 +09:00
1163ee23eb fix issues on Windows 2022-08-27 21:07:33 -07:00
36f95c0c08 improve Sequencer/Timeline UI 2022-08-28 12:58:45 +09:00
9b6eb1a08c fix File::Touch() propagation 2022-08-28 12:27:02 +09:00
beb6ca8ab9 fix indent of theme.hh 2022-08-28 11:19:49 +09:00
622b89820c implement lock system to System/NativeFile 2022-08-28 11:18:53 +09:00
f16042ddf4 fix an issue that cannot rename items of System/Dir 2022-08-28 10:18:48 +09:00
580fcfbdd2 improve ImGui style theme 2022-08-28 02:01:18 +09:00
9b37d2595b fix compile errors on Windows 2022-08-27 09:19:39 -07:00
d41fd1c47b fix issues of LuaJIT/Node 2022-08-28 01:03:33 +09:00
3539181650 fix an issue that LuaJIT::Thread gets into dead locked by using CreateNodeLambdaHandler 2022-08-27 19:02:21 +09:00
b8ddb82e78 add System/Event 2022-08-27 19:01:42 +09:00
b9c3f13f3b add System/Logger/Node 2022-08-27 17:13:01 +09:00
92ae573114 improve Sequencer/Timeline usability 2022-08-27 16:49:04 +09:00
53b4f11620 improve nf7::gui::FileFactory so that can submit by double click type 2022-08-27 15:41:20 +09:00
a93787b43b add System/Call 2022-08-27 12:53:02 +09:00
0c4454f623 fix nf7::GenericHistory destruction to delete commands orderedly 2022-08-27 12:04:31 +09:00
cf771824dc improve nf7::Node interface 2022-08-27 11:42:30 +09:00
901f5c4ab9 implement UpdateWidget() to Node/Imm 2022-08-27 00:31:16 +09:00
a703ba3bbc replace to nf7:yield() from a custom type of Node::Lambda in Lua script 2022-08-27 00:26:30 +09:00
2e25e6d14c add lua.load() and lua.pcall() to lua std library 2022-08-26 23:52:36 +09:00
651bad7625 add MutableMemento and improve FileHolder 2022-08-26 23:13:17 +09:00
bc3143016c improve nf7::GenericMemento 2022-08-26 22:57:00 +09:00
c578721277 allow LuaJIT/InlineNode to have custom I/O 2022-08-26 22:28:20 +09:00
231f67d5dd improve resilience to breaking changes of serialization format 2022-08-26 13:39:07 +09:00
8c25d22955 enhance sanitization of Node/Network 2022-08-25 16:37:56 +09:00
dedd32f2ef implement refreshing of file handler in System/NativeFile 2022-08-25 12:31:18 +09:00
d51fcadf82 fix unhandled exception 2022-08-25 12:31:18 +09:00
8e00af8dab enhance Exception logging 2022-08-25 12:31:18 +09:00
0f655315de implement permanentize of System/NativeFile 2022-08-25 12:31:18 +09:00
4864cbda5e add logger to System/NativeFile 2022-08-25 12:31:18 +09:00
9136b771a7 implement LoggerRef FileBase::Feature interface 2022-08-25 12:31:18 +09:00
07f7df3436 fix textbox size of LuaJIT/InlineNode on Node/Network 2022-08-25 12:31:18 +09:00
227b9fec67 add str() method to Vector on LuaJIT script 2022-08-25 12:31:18 +09:00
1713b11ac8 implement Node interface to System/NativeFile
and remove some useless headers
2022-08-25 12:31:13 +09:00
cec3f79321 implement lua object as Value
remove LuaJIT/Obj
remove nf7::luajit::Obj
add nf7::NodeRootLambda
2022-08-24 19:18:02 +09:00
cdf2760d2f remove unused field 2022-08-24 15:18:48 +09:00
ee0fc93406 fix an issue that LuaJIT/InlineNode causes segfault when parse error 2022-08-24 11:06:48 +09:00
f3b2e67170 add luajit::Ref::PushSelf() 2022-08-24 10:38:19 +09:00
3a0c6f7316 add LuaJIT/InlineNode 2022-08-24 10:27:40 +09:00
7cfbfc6f41 add gui::Window::shownInCurrentFrame() 2022-08-23 23:25:00 +09:00
267241cf8e fix issues on Sequencer/Timeline UI 2022-08-23 23:25:00 +09:00
6640cd219a fix Node/Network/Terminal's memento issue 2022-08-23 16:21:38 +09:00
5e7cd445ff fix an issue that input of Node/Network is not handled properly 2022-08-23 16:18:52 +09:00
ed837c28e4 fix an issue that Memento::Tag remains after AggregateCommand's destruction 2022-08-23 16:18:20 +09:00
8ea003ca96 implement Value/Curve's widget 2022-08-23 15:32:45 +09:00
5f21f0c926 add UpdateConfig() to nf7::DirItem 2022-08-23 15:09:06 +09:00
f90dbc295b add string utililities 2022-08-23 13:33:01 +09:00
43c0e9a887 fix an issue that tag_ remains when deleting file held by FileHolder 2022-08-23 11:19:00 +09:00
0222a41433 remove unused headers 2022-08-22 15:37:39 +09:00
a2faf0a36c fix an issue that command's destructor is called unorderdly 2022-08-22 15:27:40 +09:00
6c052e9d09 add an easy way to copy FileHolder deeply 2022-08-22 15:27:40 +09:00
121209161a replace FileRef to FileHolder 2022-08-22 14:52:15 +09:00
02cc9bf49d fix nf7::Node iface 2022-08-22 12:23:17 +09:00
017fe65f23 fix FileHolder issues 2022-08-21 16:15:52 +09:00
c8efb73853 add Value/Curve 2022-08-21 16:15:44 +09:00
92c1568455 fix an issue that changes of Node/Imm cannot be undone 2022-08-19 23:36:16 +09:00
9e0ad5714f add ExecHandle() and let File::Touch() use it 2022-08-19 23:33:39 +09:00
de4d614e14 add GenericMemento onRestore and onCommit instead of taking File pointer 2022-08-19 23:33:13 +09:00
cfc5596885 improve nf7::gui::Value 2022-08-19 23:12:15 +09:00
dbbaac5c8e rename System/ImGuiConfig -> System/ImGui
and Add root window to use docking feature easily
2022-08-19 22:40:14 +09:00
2c3a5fe182 add 'clear' to context menu of System/Logger 2022-08-18 23:02:30 +09:00
bb9b276e18 replace InputNode and OutputNode to Node/Network/Terminal 2022-08-18 22:37:34 +09:00
b7085b6ec5 implement Sequencer/Timeline's system variable instead of Session::info() 2022-08-18 10:31:12 +09:00
da4e566ef9 fix Audio/Device's ringbuffer not to override already-mixed samples 2022-08-18 09:34:36 +09:00
e46101e2ea allow ExecAsync to take a timeout
and Add time.now() and nf7:sleep() to LuaJIT environment
2022-08-18 09:34:36 +09:00
1afdf14b4d fix compiler warnings in MSVC 2022-08-17 13:10:51 +09:00
3c40cb9eef implement windows version of nf7::NativeFile 2022-08-17 13:10:51 +09:00
2baca5e917 improve luajit interface 2022-08-16 23:56:03 +09:00
845e0a453e improve FileHolderPopup 2022-08-16 16:21:31 +09:00
61bccc3acb add Sequencer/Adaptor 2022-08-16 12:03:04 +09:00
e01039b74e improve Node/Network to use nf7::FileBase 2022-08-15 14:07:49 +09:00
4e3cd1463e implement proper Abort() for composite lambdas 2022-08-15 01:42:57 +09:00
fa6870fa74 fix an issue that Parameter Panel stole a focus of timeline right before moving/resizing items 2022-08-15 01:03:36 +09:00
ba0ef29e64 fix an issue that wrong cursor is displayed at button of Sequencer's custom item 2022-08-15 00:54:08 +09:00
0f5e3c6246 add 'nf7::' prefix to flags of File::TypeInfo 2022-08-15 00:34:34 +09:00
ed970d45c6 add nf7::gui::PopupWrapper<T> 2022-08-15 00:18:52 +09:00
1300256f05 divide GUI codes from nf7::FileHolder 2022-08-15 00:18:12 +09:00
b1fc1a2a7d improve nf7::gui::FileFactory 2022-08-14 22:50:03 +09:00
540ce3bb0c add Sequencer/Call 2022-08-14 19:00:20 +09:00
cec2763e0a add ParameterPanel feature to Sequencer iface 2022-08-14 08:54:58 +09:00
5147f250a1 fix an issue that MementoRecorder doesn't record the first tag. 2022-08-13 13:58:02 +09:00
62d8bf8800 improve Sequencer iface to make it Exception free 2022-08-13 13:58:02 +09:00
9d4bb15b33 allow Node::Lambda to have nf7::Context as a parent 2022-08-13 13:58:02 +09:00
ba9cb13809 fix an issue that kRemove event occurs after removal 2022-08-13 00:49:08 +09:00
d3f245381b degrade History and it's not template now
because there's no need to make command type a template parameter
2022-08-12 14:05:08 +09:00
972f5ce0a1 implement saving children's memento in Sequencer/Timeline 2022-08-12 13:56:41 +09:00
c6308fa2a2 implement Lambda of Sequencer/Timeline 2022-08-12 11:54:18 +09:00
40112d224d move nf7::Lambda to Node::Lambda
and move depth() field to nf7::Context
2022-08-10 23:35:42 +09:00
2156312009 add parent parameter to Context constructor 2022-08-10 22:56:47 +09:00
96e73d3564 add Sequencer/Timeline 2022-08-10 22:38:10 +09:00
e0c79edc55 add nf7::gui::Popup 2022-08-08 10:45:09 +09:00
cfa5a1dd27 add SquashedHistory 2022-08-08 00:17:00 +09:00
1f87138d14 add a dummy parameter to nf7::Node::CreateLambda to avoid issues of multi inheritance 2022-08-07 12:08:23 +09:00
bab7dbba0f improve nf7::Lambda to use name instead of index 2022-08-07 11:47:14 +09:00
604cd8dc5a improve TypeInfo to display tooltip that describes the type 2022-08-06 12:50:40 +09:00
cda00cf237 add GetLambda() to nf7::Node::Editor 2022-08-05 13:57:55 +09:00
1f4d627648 implement real-time update for Node/Imm lambda 2022-08-05 13:49:16 +09:00
03c1199175 improve Lambda interface
also implemet lazy loading in Node/Network
2022-08-05 13:35:31 +09:00
3e8ffb0f72 make type selector large in FileCreatePopup 2022-08-04 22:20:40 +09:00
6f236873d7 fix Node/Network Editor to move unexpectedly after closing context menu 2022-08-04 22:17:41 +09:00
f140c68458 improve Node/Network Editor to move newly-created Node near the mouse cursor 2022-08-04 22:14:40 +09:00
f321c0282f change default type of Node/Imm 2022-08-04 22:09:08 +09:00
5f16a66e98 rename Audio/IO -> Audio/Device 2022-08-04 21:12:25 +09:00
0f2e578134 fix default samplerate of Audio/IO 2022-08-04 21:05:01 +09:00
111 changed files with 14351 additions and 5505 deletions

View File

@@ -1,17 +1,13 @@
cmake_minimum_required(VERSION 3.18)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
cmake_policy(SET CMP0077 NEW)
# ---- configuration ----
project(nf7 C CXX)
option(NF7_STATIC "link all libs statically" ON)
option(NF7_STATIC "link all libs statically" ON)
option(NF7_SANITIZE_THREAD "use thread sanitizer" OFF)
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(NF7_CXX_FLAGS
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>>:
@@ -19,18 +15,40 @@ set(NF7_CXX_FLAGS
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX /Zc:__cplusplus /external:anglebrackets /external:W0>
)
if (NF7_SANITIZE_THREAD)
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=thread -fno-omit-frame-pointer>>
)
else()
set(NF7_OPTIONS_SANITIZE
$<$<CONFIG:Debug>:$<$<CXX_COMPILER_ID:GNU>:
-fsanitize=address -fsanitize=undefined -fsanitize=leak -fno-omit-frame-pointer>>
)
endif()
set(NF7_GENERATED_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include/generated")
file(MAKE_DIRECTORY "${NF7_GENERATED_INCLUDE_DIR}")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_subdirectory(thirdparty EXCLUDE_FROM_ALL)
add_subdirectory(tool)
# ---- application ----
add_executable(nf7)
target_compile_options(nf7 PRIVATE ${NF7_CXX_FLAGS})
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
@@ -40,73 +58,106 @@ target_compile_definitions(nf7
)
target_sources(nf7
PRIVATE
init.hh
main.cc
nf7.cc
nf7.hh
theme.hh
common/aggregate_command.hh
common/async_buffer.hh
common/async_buffer_adaptor.hh
common/aggregate_promise.hh
common/audio_queue.hh
common/buffer.hh
common/dir.hh
common/dir_item.hh
common/file_ref.hh
common/factory.hh
common/file_base.hh
common/file_holder.hh
common/file_holder.cc
common/future.hh
common/generic_context.hh
common/generic_history.hh
common/generic_memento.hh
common/generic_type_info.hh
common/generic_watcher.hh
common/gl_enum.hh
common/gl_fence.hh
common/gl_obj.hh
common/gl_obj.cc
common/gl_shader_preproc.hh
common/gui_config.hh
common/gui_context.hh
common/gui_dnd.hh
common/gui_file.hh
common/gui_file.cc
common/gui_node.hh
common/gui_resizer.hh
common/gui_popup.hh
common/gui_popup.cc
common/gui_timeline.hh
common/gui_timeline.cc
common/gui_value.hh
common/gui_value.cc
common/gui_window.hh
common/history.hh
common/lambda.hh
common/lock.hh
common/life.hh
common/logger.hh
common/logger_ref.hh
common/luajit.hh
common/luajit.cc
common/luajit_obj.hh
common/luajit_nfile_importer.hh
common/luajit_queue.hh
common/luajit_ref.hh
common/luajit_thread.hh
common/luajit_thread.cc
common/luajit_thread_lambda.hh
common/memento.hh
common/native_file.hh
common/memento_recorder.hh
common/mutable_memento.hh
common/mutex.hh
common/nfile.hh
common/nfile_watcher.hh
common/node.hh
common/node_link_store.hh
common/node_root_lambda.hh
common/ptr_selector.hh
common/queue.hh
common/ring_buffer.hh
common/sequencer.hh
common/squashed_history.hh
common/task.hh
common/thread.hh
common/timed_queue.hh
common/util_algorithm.hh
common/util_string.hh
common/value.hh
common/wait_queue.hh
common/yas_audio.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/native_file_unix.cc>
$<$<PLATFORM_ID:Linux>:common/nfile_unix.cc>
$<$<PLATFORM_ID:Windows>:common/nfile_win.cc>
file/audio_context.cc
file/audio_io.cc
file/audio_device.cc
file/gl_obj.cc
file/luajit_context.cc
file/luajit_node.cc
file/luajit_obj.cc
file/node_imm.cc
file/node_network.cc
file/node_ref.cc
file/sequencer_adaptor.cc
file/sequencer_call.cc
file/sequencer_timeline.cc
file/system_call.cc
file/system_dir.cc
file/system_imgui_config.cc
file/system_event.cc
file/system_imgui.cc
file/system_logger.cc
file/system_native_file.cc
file/system_nfile.cc
file/value_curve.cc
file/value_plot.cc
)
target_link_libraries(nf7
PRIVATE
@@ -117,18 +168,9 @@ target_link_libraries(nf7
implot
linalg.h
luajit
magic_enum
miniaudio
source_location
yas
yaml-cpp
)
# ---- resource compile ----
set(NF7_DEFAULT_ROOT_INC "${NF7_GENERATED_INCLUDE_DIR}/root.nf7.inc")
add_custom_command(
OUTPUT ${NF7_DEFAULT_ROOT_INC}
DEPENDS nf7-init
COMMAND $<TARGET_FILE:nf7-init> > ${NF7_DEFAULT_ROOT_INC}
COMMENT "generating root.nf7..."
VERBATIM
)
target_sources(nf7 PRIVATE ${NF7_DEFAULT_ROOT_INC})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

27
common/factory.hh Normal file
View File

@@ -0,0 +1,27 @@
#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

62
common/file_base.hh Normal file
View File

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

134
common/file_holder.cc Normal file
View File

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

188
common/file_holder.hh Normal file
View File

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

View File

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

View File

@@ -24,17 +24,25 @@ namespace nf7 {
// 3. Call Promise::Return(T) or Promise::Throw() to finish the promise.
//
// Users who receive Future can wait for finishing
// by Future::Then(), Future::ThenSub(), or co_await.
// by Future::Then(), Future::ThenIf(), Future::Catch(), or co_await.
class CoroutineAbortException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
// T must not be void, use std::monostate instead
template <typename T>
class Future final {
public:
class Promise;
class Coro;
using Handle = std::coroutine_handle<Promise>;
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, };
@@ -58,7 +66,7 @@ class Future final {
// Factory side have this to tell finish or abort.
class Promise final {
public:
// Use data_() instead, MSVC doesn't allow this:
// 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;
@@ -89,11 +97,14 @@ class Future final {
auto Return(T&& v) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->state = kDone;
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);
@@ -103,21 +114,25 @@ class Future final {
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 (Exception&) {
} catch (...) {
Throw(std::current_exception());
}
// thread-safe
// Creates Future() object.
Future future() const noexcept {
ThisFuture future() const noexcept {
assert(data_);
return Future(data_);
return ThisFuture(data_);
}
auto get_return_object() noexcept {
@@ -178,14 +193,14 @@ class Future final {
Coro& operator=(const Coro&) = delete;
Coro& operator=(Coro&&) = default;
Future Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ThisFuture Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ctx->env().ExecSub(ctx, [h = h_]() { h.resume(); });
data_->ctx = ctx;
return Future(data_);
return ThisFuture(data_);
}
void Abort() noexcept {
h_.promise().Throw(
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
std::make_exception_ptr<CoroutineAbortException>({"coroutine aborted"}));
data_->aborted = true;
}
@@ -203,43 +218,130 @@ class Future final {
}
Future(std::exception_ptr e) noexcept : imm_({e}) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
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.
Future& Then(std::function<void(Future)>&& f) noexcept {
// 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(
[d = data_, f = std::move(f)]() { f(Future(d)); });
[fun = std::move(fun), d = data_]() mutable { fun(ThisFuture {d}); });
return *this;
}
}
f(*this);
fun(*this);
return *this;
}
// Schedules to execute f() as a sub task when the promise is finished or aborted.
Future& ThenSub(const std::shared_ptr<nf7::Context>& ctx,
std::function<void(Future)>&& f) noexcept {
if (data_) {
std::unique_lock<std::mutex> k(data_->mtx);
if (yet()) {
data_->recv.push_back([d = data_, ctx, f = std::move(f)]() {
ctx->env().ExecSub(ctx, std::bind(f, Future(d)));
});
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());
}
}
ctx->env().ExecSub(ctx, std::bind(f, Future(data_)));
return *this;
});
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));
}
auto& value() {
// 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_));
@@ -262,11 +364,14 @@ class Future final {
return !imm_ && data_->state == kYet;
}
bool done() const noexcept {
return (imm_ && std::holds_alternative<T>(*imm_)) || data_->state == kDone;
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_->state == kError;
return
(imm_ && std::holds_alternative<std::exception_ptr>(*imm_)) ||
(data_ && data_->state == kError);
}
bool await_ready() const noexcept { return !yet(); }
@@ -278,7 +383,6 @@ class Future final {
std::unique_lock<std::mutex> k(data.mtx);
auto callee_ctx = data.ctx.lock();
assert(callee_ctx);
auto caller_data = caller.promise().data__();
auto caller_ctx = caller_data->ctx.lock();
@@ -301,10 +405,10 @@ class Future final {
caller.resume();
}
}
auto await_resume() { return value(); }
auto& await_resume() { return value(); }
private:
std::optional<std::variant<T, std::exception_ptr>> imm_;
std::optional<Imm> imm_;
std::shared_ptr<Data> data_;
Future(const std::shared_ptr<Data>& data) noexcept : data_(data) { }

View File

@@ -10,13 +10,18 @@
namespace nf7 {
class GenericContext : public Context {
class GenericContext : public nf7::Context {
public:
GenericContext(Env& env, File::Id id, std::string_view desc = "") noexcept :
Context(env, id), desc_(desc) {
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(File& f, std::string_view desc = "") noexcept :
GenericContext(f.env(), f.id(), 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 {

View File

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

View File

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

View File

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

327
common/gl_enum.hh Normal file
View File

@@ -0,0 +1,327 @@
#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

44
common/gl_fence.hh Normal file
View File

@@ -0,0 +1,44 @@
#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

405
common/gl_obj.cc Normal file
View File

@@ -0,0 +1,405 @@
#include "common/gl_obj.hh"
#include <algorithm>
#include <array>
#include <exception>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <vector>
#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 {
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 {
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 {
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";
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&) {
// 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());
}
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));
}
}
void Obj_ProgramMeta::RevertState() const noexcept {
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 {
// 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::vector<gl::Buffer::Factory::Product> attrs_fu;
attrs_fu.reserve(attrs.size());
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};
attrs_fu.push_back(Lock(attr.buffer, gl::BufferTarget::Array, required));
apro.Add(attrs_fu.back());
}
// 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 {
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>();
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 Normal file
View File

@@ -0,0 +1,236 @@
#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

114
common/gl_shader_preproc.hh Normal file
View File

@@ -0,0 +1,114 @@
#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

59
common/gui_config.hh Normal file
View File

@@ -0,0 +1,59 @@
#include <string>
#include <type_traits>
#include <imgui.h>
#include <imgui_stdlib.h>
#include "nf7.hh"
#include "common/generic_memento.hh"
namespace nf7::gui {
template <typename T>
concept ConfigData = requires (T& x) {
{ x.Stringify() } -> std::convertible_to<std::string>;
x.Parse(std::string {});
};
template <ConfigData T>
void Config(nf7::GenericMemento<T>& mem) noexcept {
static std::string text_;
static std::string msg_;
static bool mod_;
if (ImGui::IsWindowAppearing()) {
text_ = mem->Stringify();
msg_ = "";
mod_ = false;
}
mod_ |= ImGui::InputTextMultiline("##config", &text_);
ImGui::BeginDisabled(!mod_);
if (ImGui::Button("apply")) {
try {
mem->Parse(text_);
mem.Commit();
msg_ = "";
mod_ = false;
} catch (nf7::Exception& e) {
msg_ = e.msg();
}
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("restore")) {
text_ = mem->Stringify();
msg_ = "";
mod_ = false;
}
if (msg_.size()) {
ImGui::Bullet();
ImGui::TextUnformatted(msg_.c_str());
}
}
} // namespace nf7::gui

46
common/gui_context.hh Normal file
View File

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

View File

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

252
common/gui_file.cc Normal file
View File

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

View File

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

View File

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

48
common/gui_popup.cc Normal file
View File

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

67
common/gui_popup.hh Normal file
View File

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

View File

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

390
common/gui_timeline.cc Normal file
View File

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

206
common/gui_timeline.hh Normal file
View File

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

115
common/gui_value.cc Normal file
View File

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

122
common/gui_value.hh Normal file
View File

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

View File

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

View File

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

View File

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

76
common/life.hh Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cinttypes>
#include <cctype>
#include <string>
@@ -12,14 +13,11 @@
#include "common/logger.hh"
#include "common/logger_ref.hh"
#include "common/luajit_thread.hh"
#include "common/luajit_std.hh"
namespace nf7::luajit {
// pushes original libraries
static void PushMathLib(lua_State* L) noexcept;
static void PushTableLib(lua_State* L) noexcept;
// buffer <-> lua value conversion
template <typename T>
static size_t PushArrayFromBytes(
@@ -30,64 +28,6 @@ template <typename T>
static size_t ToBytes(lua_State* L, uint8_t* ptr, uint8_t* end);
void PushGlobalTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::PushGlobalTable")) {
PushMathLib(L);
lua_setfield(L, -2, "math");
PushTableLib(L);
lua_setfield(L, -2, "table");
lua_pushcfunction(L, [](auto L) {
PushValue(L, CheckValue(L, 1));
return 1;
});
lua_setfield(L, -2, "nf7_Value");
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
return 1;
}
if (auto mut = ToMutableVector(L, 1)) {
PushVector(L, std::make_shared<std::vector<uint8_t>>(std::move(*mut)));
return 1;
}
return luaL_error(L, "expected nf7::Value::MutableVector or nf7::Value::Vector");
});
lua_setfield(L, -2, "nf7_Vector");
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
if (imm->use_count() == 1) {
PushMutableVector(L, std::move(**imm));
} else {
PushMutableVector(L, std::vector<uint8_t> {**imm});
}
return 1;
}
if (auto mut = ToMutableVector(L, 1)) {
PushMutableVector(L, std::vector<uint8_t> {*mut});
return 1;
}
PushMutableVector(L, {});
return 1;
});
lua_setfield(L, -2, "nf7_MutableVector");
}
}
void PushImmEnv(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::PushImmEnv")) {
lua_createtable(L, 0, 0);
PushGlobalTable(L);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) { return luaL_error(L, "global is immutable"); });
lua_setfield(L, -2, "__newindex");
lua_setmetatable(L, -2);
}
}
void PushValue(lua_State* L, const nf7::Value& v) noexcept {
new (lua_newuserdata(L, sizeof(v))) nf7::Value(v);
@@ -106,15 +46,15 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
struct Visitor final {
lua_State* L;
const nf7::Value& v;
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); }
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); }
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); }
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); }
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); }
auto operator()(Value::Vector) noexcept { lua_pushnil(L); }
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
auto operator()(Value::Pulse) noexcept { lua_pushnil(L); }
auto operator()(Value::Boolean) noexcept { lua_pushboolean(L, v.boolean()); }
auto operator()(Value::Integer) noexcept { lua_pushinteger(L, v.integer()); }
auto operator()(Value::Scalar) noexcept { lua_pushnumber(L, v.scalar()); }
auto operator()(Value::String) noexcept { lua_pushstring(L, v.string().c_str()); }
auto operator()(Value::ConstVector) noexcept { PushVector(L, v.vector()); }
auto operator()(Value::DataPtr) noexcept { lua_pushnil(L); }
auto operator()(Value::Tuple) noexcept {
auto operator()(Value::ConstTuple) noexcept {
const auto& tup = *v.tuple();
lua_createtable(L, 0, 0);
size_t arridx = 0;
@@ -142,14 +82,65 @@ void PushValue(lua_State* L, const nf7::Value& v) noexcept {
}
lua_setmetatable(L, -2);
}
void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
assert(v);
new (lua_newuserdata(L, sizeof(v))) nf7::Value::Vector(v);
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 (luaL_newmetatable(L, "nf7::Value::Vector")) {
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<nf7::Value::Vector>(L, 1, "nf7::Value::Vector");
const auto& v = CheckRef<T>(L, 1, kTypeName);
const auto offset = luaL_checkinteger(L, 2);
if (offset < 0) {
return luaL_error(L, "negative offset");
@@ -170,7 +161,7 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
if (lua_istable(L, -1)) { // array
lua_rawgeti(L, -1, 1);
const std::string_view type = luaL_checkstring(L, -1);
lua_rawgeti(L, -1, 2);
lua_rawgeti(L, -2, 2);
const size_t n = static_cast<size_t>(luaL_checkinteger(L, -1));
lua_pop(L, 2);
@@ -229,7 +220,14 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
lua_setfield(L, -2, "get");
lua_pushcfunction(L, [](auto L) {
const auto& v = CheckRef<nf7::Value::Vector>(L, 1, "nf7::Value::Vector");
const auto& v = CheckRef<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;
});
@@ -237,20 +235,23 @@ void PushVector(lua_State* L, const nf7::Value::Vector& v) noexcept {
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, [](auto L) {
CheckRef<nf7::Value::Vector>(L, 1, "nf7::Value::Vector").~shared_ptr();
CheckRef<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 {
new (lua_newuserdata(L, sizeof(v))) std::vector<uint8_t>(std::move(v));
if (luaL_newmetatable(L, "nf7::Value::MutableVector")) {
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<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
auto& v = CheckRef<T>(L, 1, kTypeName);
const lua_Integer offset = luaL_checkinteger(L, 2);
if (offset < 0) return luaL_error(L, "negative offset");
@@ -294,17 +295,110 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
lua_setfield(L, -2, "set");
lua_pushcfunction(L, [](auto L) {
auto& v = CheckRef<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector");
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<std::vector<uint8_t>>(L, 1, "nf7::Value::MutableVector").~vector();
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);
}
});
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");
@@ -313,106 +407,24 @@ void PushMutableVector(lua_State* L, std::vector<uint8_t>&& v) noexcept {
}
std::optional<nf7::Value> ToValue(lua_State* L, int idx) noexcept {
if (lua_isnoneornil(L, idx)) {
return nf7::Value {nf7::Value::Pulse {}};
void PushGlobalTable(lua_State* L) noexcept {
if (luaL_newmetatable(L, "nf7::luajit::GlobalTable")) {
PushStdTable(L);
lua_setfield(L, -2, "std");
}
if (lua_isnumber(L, idx)) {
const double n = lua_tonumber(L, idx);
const auto i = static_cast<nf7::Value::Integer>(n);
return n == static_cast<double>(i)? nf7::Value {i}: nf7::Value{n};
}
if (lua_isboolean(L, idx)) {
return nf7::Value {bool {!!lua_toboolean(L, idx)}};
}
if (lua_isstring(L, idx)) {
size_t len;
const char* str = lua_tolstring(L, idx, &len);
return nf7::Value {std::string {str, len}};
}
if (auto vec = ToVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (auto vec = ToMutableVector(L, idx)) {
return nf7::Value {std::move(*vec)};
}
if (lua_istable(L, idx)) {
std::vector<nf7::Value::TuplePair> tup;
lua_pushnil(L);
while (lua_next(L, idx)) {
std::string name = "";
if (lua_isstring(L, -2)) {
name = lua_tostring(L, -2);
}
auto val = ToValue(L, -1);
if (!val) return std::nullopt;
tup.push_back({std::move(name), std::move(*val)});
lua_pop(L, 1);
}
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");
}
return nf7::Value {std::move(tup)};
lua_setmetatable(L, -2);
}
if (auto val = ToRef<nf7::Value>(L, idx, "nf7::Value")) {
return *val;
}
return std::nullopt;
}
std::optional<nf7::Value::Vector> ToVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<nf7::Value::Vector>(L, idx, "nf7::Value::Vector");
if (!ptr) return std::nullopt;
return *ptr;
}
std::optional<std::vector<uint8_t>> ToMutableVector(lua_State* L, int idx) noexcept {
auto ptr = ToRef<std::vector<uint8_t>>(L, idx, "nf7::Value::MutableVector");
if (!ptr) return std::nullopt;
return std::move(*ptr);
}
static void PushMathLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "sin");
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "cos");
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "tan");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
static void PushTableLib(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// table.setmetatable(table, meta_table)
lua_pushcfunction(L, [](auto L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TTABLE);
lua_settop(L, 2);
lua_setmetatable(L, 1);
return 1;
});
lua_setfield(L, -2, "setmetatable");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
@@ -433,6 +445,7 @@ static size_t PushArrayFromBytes(lua_State* L, size_t n, const uint8_t* ptr, con
[] <bool F = false>() { static_assert(F, "T is invalid"); }();
}
lua_rawseti(L, -2, static_cast<int>(i + 1));
ptr += sizeof(T);
}
return size;
}

View File

@@ -9,22 +9,91 @@
#include <lua.hpp>
#include "common/node_root_lambda.hh"
#include "common/value.hh"
namespace nf7::luajit {
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
// ---- 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;
void PushVector(lua_State*, const nf7::Value::Vector&) noexcept;
void PushMutableVector(lua_State*, std::vector<uint8_t>&&) noexcept;
std::optional<nf7::Value> ToValue(lua_State*, int) noexcept;
std::optional<nf7::Value::Vector> ToVector(lua_State*, int) noexcept;
std::optional<std::vector<uint8_t>> ToMutableVector(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) {
@@ -52,7 +121,11 @@ inline void Push(lua_State* L, const std::vector<uint8_t>& v) noexcept {
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;
}
@@ -67,50 +140,8 @@ int PushAll(lua_State* L, T v, Args&&... args) noexcept {
}
template <typename T>
inline void PushWeakPtr(lua_State* L, const std::weak_ptr<T>& wptr) noexcept {
new (lua_newuserdata(L, sizeof(wptr))) std::weak_ptr<T>(wptr);
}
template <typename T>
inline void PushWeakPtrDeleter(lua_State* L, const std::weak_ptr<T>& = {}) noexcept {
lua_pushcfunction(L, [](auto L) {
reinterpret_cast<std::weak_ptr<T>*>(lua_touserdata(L, 1))->~weak_ptr();
return 0;
});
}
inline bool MatchMetaName(lua_State* L, int idx, const char* type) noexcept {
if (0 == lua_getmetatable(L, idx)) {
return false;
}
luaL_getmetatable(L, type);
const bool ret = lua_rawequal(L, -1, -2);
lua_pop(L, 2);
return ret;
}
template <typename T>
inline T* ToRef(lua_State* L, int idx, const char* type) noexcept {
return MatchMetaName(L, idx, type)? reinterpret_cast<T*>(lua_touserdata(L, idx)): nullptr;
}
template <typename T>
inline std::shared_ptr<T> CheckWeakPtr(lua_State* L, int idx, const char* type) {
auto ptr = reinterpret_cast<std::weak_ptr<T>*>(luaL_checkudata(L, idx, type));
if (auto ret = ptr->lock()) {
return ret;
} else {
luaL_error(L, "object expired: %s", typeid(T).name());
return nullptr;
}
}
template <typename T>
inline T& CheckRef(lua_State* L, int idx, const char* type) {
return *reinterpret_cast<T*>(luaL_checkudata(L, idx, type));
}
inline 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);
}
// ---- global table
void PushGlobalTable(lua_State*) noexcept;
void PushImmEnv(lua_State*) noexcept;
} // namespace nf7

View File

@@ -0,0 +1,107 @@
#pragma once
#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 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.ctx()->initiator(),
"LuaJIT imported script (nfile)", th.ctx());
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_};
std::filesystem::file_time_type ret = {};
for (const auto& p : imports_) {
try {
ret = std::max(ret, std::filesystem::last_write_time(p));
} catch (std::filesystem::filesystem_error&) {
}
}
return ret;
}
private:
const std::filesystem::path base_;
mutable std::mutex mtx_;
std::vector<std::string> imports_;
void AddImport(const std::filesystem::path& p) noexcept {
auto str = p.string();
std::unique_lock<std::mutex> _ {mtx_};
if (imports_.end() == std::find(imports_.begin(), imports_.end(), str)) {
imports_.emplace_back(std::move(str));
}
}
};
} // namespace nf7::luajit

View File

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

View File

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

View File

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

161
common/luajit_std.hh Normal file
View File

@@ -0,0 +1,161 @@
#include <lua.hpp>
#include "common/luajit.hh"
namespace nf7::luajit {
inline void PushStdTable(lua_State* L) noexcept {
lua_newuserdata(L, 0);
lua_createtable(L, 0, 0);
lua_createtable(L, 0, 0);
{
// ---- lua lib ----
// assert(expr[, msg])
lua_pushcfunction(L, [](auto L) {
if (lua_toboolean(L, 1)) {
return 0;
}
if (lua_gettop(L) >= 2) {
return luaL_error(L, lua_tostring(L, 2));
} else {
return luaL_error(L, "assertion failure");
}
});
lua_setfield(L, -2, "assert");
// error(msg)
lua_pushcfunction(L, [](auto L) {
return luaL_error(L, luaL_checkstring(L, 1));
});
lua_setfield(L, -2, "error");
// load(str)
lua_pushcfunction(L, [](auto L) {
if (0 != luaL_loadstring(L, luaL_checkstring(L, 1))) {
return luaL_error(L, "lua.load error: %s", lua_tostring(L, -1));
}
return 1;
});
lua_setfield(L, -2, "load");
// pcall(func, args...) -> success, result
lua_pushcfunction(L, [](auto L) {
if (0 == lua_pcall(L, lua_gettop(L)-1, LUA_MULTRET, 0)) {
lua_pushboolean(L, true);
lua_insert(L, 1);
return lua_gettop(L);
} else {
lua_pushboolean(L, false);
lua_insert(L, 1);
return 2;
}
});
lua_setfield(L, -2, "pcall");
// ---- math lib ----
// sin(theta)
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::sin(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "sin");
// cos(theta)
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::cos(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "cos");
// tan(theta)
lua_pushcfunction(L, [](auto L) {
lua_pushnumber(L, std::tan(luaL_checknumber(L, 1)));
return 1;
});
lua_setfield(L, -2, "tan");
// ---- table lib ----
// meta(table, meta_table)
lua_pushcfunction(L, [](auto L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TTABLE);
lua_settop(L, 2);
lua_setmetatable(L, 1);
return 1;
});
lua_setfield(L, -2, "meta");
// ---- time lib ----
// now()
lua_pushcfunction(L, [](auto L) {
const auto now = nf7::Env::Clock::now().time_since_epoch();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now);
lua_pushnumber(L, static_cast<double>(ms.count())/1000.);
return 1;
});
lua_setfield(L, -2, "now");
// ---- value lib ----
// value(entity) -> value
lua_pushcfunction(L, [](auto L) {
if (lua_isstring(L, 2)) {
const auto type = std::string_view {lua_tostring(L, 2)};
if (type == "integer" || type == "int") {
PushValue(L, static_cast<nf7::Value::Integer>(luaL_checkinteger(L, 1)));
} else {
return luaL_error(L, "unknown type specifier: %s", type);
}
} else {
PushValue(L, CheckValue(L, 1));
}
return 1;
});
lua_setfield(L, -2, "value");
// mvector(vector or mutable vector) -> mutable vector
lua_pushcfunction(L, [](auto L) {
if (auto imm = ToVector(L, 1)) {
if (imm->use_count() == 1) {
PushMutableVector(L, std::move(const_cast<std::vector<uint8_t>&>(**imm)));
} else {
PushMutableVector(L, std::vector<uint8_t> {**imm});
}
return 1;
} else if (auto mut = ToMutableVector(L, 1)) {
PushMutableVector(L, std::vector<uint8_t> {*mut});
return 1;
} else {
PushMutableVector(L, {});
return 1;
}
});
lua_setfield(L, -2, "mvector");
// ---- bit manip ----
luaL_openlibs(L);
luaL_loadstring(L, "return require(\"bit\")");
lua_call(L, 0, 1);
lua_setfield(L, -2, "bit");
// ---- str manip ----
luaL_openlibs(L);
luaL_loadstring(L, "return string");
lua_call(L, 0, 1);
lua_setfield(L, -2, "str");
}
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
} // namespace nf7::luajit

View File

@@ -1,12 +1,12 @@
#include "common/luajit_thread.hh"
#include "common/luajit_thread_lambda.hh"
#include "common/luajit_thread_lock.hh"
#include <chrono>
#include <sstream>
#include <tuple>
#include <unordered_set>
#include "common/async_buffer.hh"
#include "common/luajit_obj.hh"
#include "common/node.hh"
#include "common/node_root_lambda.hh"
namespace nf7::luajit {
@@ -18,18 +18,12 @@ constexpr size_t kBufferSizeMax = 64 * 1024 * 1024;
// Pushes a metatable for Thread object, available on global table as 'nf7'.
static void PushMeta(lua_State*) noexcept;
// Pushes Lua object built by a file who implements luajit::Obj interface.
static void GetLuaObjAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, File& f);
lua_State* Thread::Init(lua_State* L) noexcept {
assert(state_ == kInitial);
th_ = lua_newthread(L);
PushImmEnv(L);
lua_setfenv(L, -2);
th_ref_.emplace(ctx_, ljq_, luaL_ref(L, LUA_REGISTRYINDEX));
th_ref_.emplace(ctx_, ljq_, L);
state_ = kPaused;
return th_;
@@ -40,24 +34,24 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
if (state_ == kAborted) return;
assert(L == th_);
assert(state_ == kPaused);
(void) L;
static const auto kHook = [](auto L, auto) {
luaL_error(L, "reached instruction limit (<=1e7)");
};
lua_sethook(th_, kHook, LUA_MASKCOUNT, kInstructionLimit);
lua_sethook(L, kHook, LUA_MASKCOUNT, kInstructionLimit);
PushGlobalTable(th_);
PushWeakPtr(th_, weak_from_this());
PushMeta(th_);
lua_setmetatable(th_, -2);
lua_setfield(th_, -2, "nf7");
lua_pop(th_, 1);
// 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;
k.unlock();
active_ = true;
const auto ret = lua_resume(th_, narg);
const auto ret = lua_resume(L, narg);
active_ = false;
k.lock();
if (state_ == kAborted) return;
@@ -72,7 +66,7 @@ void Thread::Resume(lua_State* L, int narg) noexcept {
state_ = kAborted;
}
if (!std::exchange(skip_handle_, false)) {
handler_(*this, th_);
handler_(*this, L);
}
}
void Thread::Abort() noexcept {
@@ -81,13 +75,83 @@ void Thread::Abort() noexcept {
}
Thread::Handler Thread::CreateNodeLambdaHandler(
const std::shared_ptr<nf7::Node::Lambda>& caller,
const std::shared_ptr<nf7::Node::Lambda>& callee) noexcept {
return [caller, callee](auto& th, auto L) {
switch (th.state()) {
case nf7::luajit::Thread::kPaused:
switch (lua_gettop(L)) {
case 0:
th.ExecResume(L);
return;
case 2:
if (auto v = nf7::luajit::ToValue(L, 2)) {
auto k = luaL_checkstring(L, 1);
caller->env().ExecSub(
caller, [caller, callee, k = std::string {k}, v = std::move(v)]() {
caller->Handle(k, *v, callee);
});
th.ExecResume(L);
return;
} else {
}
/* FALLTHROUGH */
default:
if (auto log = th.logger()) {
log->Warn("invalid use of yield, nf7:yield() or nf7:yield(name, value)");
}
th.ExecResume(L);
return;
}
case nf7::luajit::Thread::kFinished:
return;
default:
if (auto log = th.logger()) {
log->Warn(std::string {"luajit execution error: "}+lua_tostring(L, -1));
}
return;
}
};
}
static void PushMeta(lua_State* L) noexcept {
if (luaL_newmetatable(L, Thread::kTypeName)) {
PushWeakPtrDeleter<Thread>(L);
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);
@@ -101,11 +165,21 @@ static void PushMeta(lua_State* L) noexcept {
th->ExecResume(L, 0);
}
});
th->ExpectYield(L);
return lua_yield(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->ctx(), th->ljq(), L);
PushValue(L, nf7::Value {std::move(ref)});
return 1;
});
lua_setfield(L, -2, "ref");
// nf7:query(file_id, interface)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
@@ -115,14 +189,10 @@ static void PushMeta(lua_State* L) noexcept {
th->env().ExecSub(th->ctx(), [th, L, id, iface = std::move(iface)]() {
try {
auto& f = th->env().GetFileOrThrow(static_cast<nf7::File::Id>(id));
if (iface == "buffer") {
Thread::Lock<nf7::AsyncBuffer>::AcquireAndPush(L, th, f, false);
} else if (iface == "exbuffer") {
Thread::Lock<nf7::AsyncBuffer>::AcquireAndPush(L, th, f, true);
} else if (iface == "lua") {
GetLuaObjAndPush(L, th, f);
} else if (iface == "node") {
Thread::Lambda::CreateAndPush(L, th, f);
if (iface == "node") {
th->ExecResume(
L, nf7::NodeRootLambda::Create(
th->ctx(), f.template interfaceOrThrow<nf7::Node>()));
} else {
throw nf7::Exception {"unknown interface: "+iface};
}
@@ -130,16 +200,25 @@ static void PushMeta(lua_State* L) noexcept {
th->ExecResume(L, nullptr, e.msg());
}
});
th->ExpectYield(L);
return lua_yield(L, 0);
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->ctx(), [th, L](auto) { th->ExecResume(L); }, time);
return th->Yield(L);
});
lua_setfield(L, -2, "sleep");
// nf7:yield(results...)
lua_pushcfunction(L, [](auto L) {
auto th = Thread::GetPtr(L, 1);
th->ExecResume(L);
th->ExpectYield(L);
return lua_yield(L, lua_gettop(L)-1);
});
lua_setfield(L, -2, "yield");
@@ -153,7 +232,11 @@ static void PushMeta(lua_State* L) noexcept {
const int n = lua_gettop(L);
std::stringstream st;
for (int i = 2; i <= n; ++i) {
st << lua_tostring(L, i);
if (auto msg = lua_tostring(L, i)) {
st << msg;
} else {
return luaL_error(L, "cannot stringify %s", luaL_typename(L, i));
}
}
logger->Write({lv, st.str()});
return 0;
@@ -171,23 +254,4 @@ static void PushMeta(lua_State* L) noexcept {
}
}
static void GetLuaObjAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, File& f) {
f.interfaceOrThrow<nf7::luajit::Obj>().Build().
Then([th, L](auto fu) {
th->ljq()->Push(th->ctx(), [th, L, fu](auto) mutable {
try {
const auto& obj = fu.value();
if (th->ljq() != obj->ljq()) {
throw nf7::Exception {"the object is built on other LuaJIT context"};
}
lua_rawgeti(L, LUA_REGISTRYINDEX, obj->index());
th->Resume(L, 1);
} catch (nf7::Exception& e) {
th->Resume(L, luajit::PushAll(L, nullptr, e.msg()));
}
});
});
}
} // namespace nf7::luajit

View File

@@ -5,6 +5,7 @@
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@@ -14,10 +15,10 @@
#include "nf7.hh"
#include "common/future.hh"
#include "common/lambda.hh"
#include "common/logger_ref.hh"
#include "common/luajit.hh"
#include "common/luajit_ref.hh"
#include "common/node.hh"
namespace nf7::luajit {
@@ -29,34 +30,40 @@ class Thread final : public std::enable_shared_from_this<Thread> {
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
using Handler = std::function<void(Thread&, lua_State*)>;
// Registry keeps an objects used in the Thread and deletes immediately when the Thread ends.
class RegistryItem;
class Lambda;
template <typename T> class Lock;
class Importer;
class Exception final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
// Creates a handler to finalize a promise.
// Creates a handler that finalizes a promise.
template <typename T>
static Handler CreatePromiseHandler(
static inline Handler CreatePromiseHandler(
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&&) noexcept;
// Creates a handler 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 = CheckWeakPtr<Thread>(L, idx, kTypeName);
th->EnsureActive(L);
return th;
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>& ctx,
const std::shared_ptr<nf7::luajit::Queue>& ljq,
const std::shared_ptr<nf7::Lambda::Owner>& la_owner,
Handler&& handler) noexcept :
ctx_(ctx), ljq_(ljq), la_owner_(la_owner), handler_(std::move(handler)) {
ctx_(ctx), ljq_(ljq), handler_(std::move(handler)) {
}
Thread(const Thread&) = delete;
Thread(Thread&&) = delete;
@@ -67,6 +74,15 @@ class Thread final : public std::enable_shared_from_this<Thread> {
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;
@@ -76,9 +92,10 @@ class Thread final : public std::enable_shared_from_this<Thread> {
void Resume(lua_State* L, int narg) noexcept;
// must be called on luajit thread
// handler_ won't be called on next yielding
void ExpectYield(lua_State*) noexcept {
// handler_ won't be called on this yielding
int Yield(lua_State* L) {
skip_handle_ = true;
return lua_yield(L, 0);
}
// must be called on luajit thread
@@ -88,17 +105,6 @@ class Thread final : public std::enable_shared_from_this<Thread> {
}
}
// must be called on luajit thread
void Register(lua_State*, const std::shared_ptr<RegistryItem>& item) noexcept {
registry_.push_back(item);
}
void Forget(lua_State*, const RegistryItem& item) noexcept {
registry_.erase(
std::remove_if(registry_.begin(), registry_.end(),
[&item](auto& x) { return x.get() == &item; }),
registry_.end());
}
// thread-safe
void Abort() noexcept;
@@ -112,11 +118,11 @@ class Thread final : public std::enable_shared_from_this<Thread> {
});
}
nf7::Env& env() noexcept { return ctx_->env(); }
nf7::Env& env() const noexcept { return ctx_->env(); }
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; }
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
const std::shared_ptr<nf7::Lambda::Owner>& lambdaOwner() const noexcept { return la_owner_; }
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
State state() const noexcept { return state_; }
private:
@@ -125,7 +131,6 @@ class Thread final : public std::enable_shared_from_this<Thread> {
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
std::shared_ptr<nf7::Lambda::Owner> la_owner_;
Handler handler_;
std::atomic<State> state_ = kInitial;
@@ -138,40 +143,43 @@ class Thread final : public std::enable_shared_from_this<Thread> {
// installed features
std::shared_ptr<nf7::LoggerRef> logger_;
std::shared_ptr<Importer> importer_;
// mutable params
std::vector<std::shared_ptr<RegistryItem>> registry_;
bool active_ = false; // true while executing lua_resume
bool skip_handle_ = false; // handler_ won't be called on next yield
};
class Thread::RegistryItem {
class Thread::Importer {
public:
RegistryItem() = default;
virtual ~RegistryItem() = default;
RegistryItem(const RegistryItem&) = delete;
RegistryItem(RegistryItem&&) = delete;
RegistryItem& operator=(const RegistryItem&) = delete;
RegistryItem& operator=(RegistryItem&&) = delete;
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 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, f = std::move(f)](auto& self, auto L) {
return [pro = pro, f = std::move(f)](auto& self, auto L) mutable {
switch (self.state()) {
case kPaused:
pro.Throw(std::make_exception_ptr<nf7::Exception>({"unexpected yield"}));
pro.template Throw<nf7::Exception>("unexpected yield");
break;
case kFinished:
pro.Wrap([&]() { return f(L); });
break;
case kAborted:
pro.Throw(std::make_exception_ptr<nf7::Exception>({lua_tostring(L, -1)}));
pro.template Throw<nf7::Exception>(lua_tostring(L, -1));
break;
default:
assert(false);

View File

@@ -1,230 +0,0 @@
#pragma once
#include "common/luajit_thread.hh"
#include <algorithm>
#include <deque>
#include <memory>
#include <span>
#include <string>
#include <vector>
#include "common/luajit.hh"
#include "common/node.hh"
namespace nf7::luajit {
class Thread::Lambda final : public Thread::RegistryItem,
public std::enable_shared_from_this<Thread::Lambda> {
public:
static constexpr const char* kTypeName = "nf7::luajit::Thread::Lambda";
static void CreateAndPush(
lua_State* L, const std::shared_ptr<Thread>& th, nf7::File& f) {
auto la = std::make_shared<Thread::Lambda>(th, f.interfaceOrThrow<nf7::Node>());
th->ljq()->Push(th->ctx(), [L, th, la](auto) {
th->Register(L, la);
la->Push(L);
th->Resume(L, 1);
});
}
static std::shared_ptr<Thread::Lambda> GetPtr(lua_State* L, int idx) {
auto self = luajit::CheckWeakPtr<Thread::Lambda>(L, idx, kTypeName);
self->GetThread(L)->EnsureActive(L);
return self;
}
// must be created on main thread
explicit Lambda(const std::shared_ptr<Thread>& th, nf7::Node& n) noexcept;
void Push(lua_State* L) noexcept {
luajit::PushWeakPtr<Thread::Lambda>(L, shared_from_this());
PushMeta(L);
lua_setmetatable(L, -2);
}
private:
std::weak_ptr<Thread> th_;
struct ImmData {
ImmData(std::span<const std::string> i, std::span<const std::string> o) noexcept :
in(i.begin(), i.end()), out(o.begin(), o.end()) {
}
std::vector<std::string> in, out;
};
std::shared_ptr<const ImmData> imm_;
class Receiver;
std::shared_ptr<Receiver> recv_;
std::shared_ptr<nf7::Lambda> la_;
std::shared_ptr<Thread> GetThread(lua_State* L) {
if (auto th = th_.lock()) {
return th;
} else {
luaL_error(L, "thread expired");
return nullptr;
}
}
static inline void PushMeta(lua_State* L) noexcept;
static inline size_t GetIndex(lua_State* L, int v, std::span<const std::string> names);
};
// Receives an output from targetted lambda and Resumes the Thread.
class Thread::Lambda::Receiver final : public nf7::Lambda,
public std::enable_shared_from_this<Thread::Lambda::Receiver> {
public:
static constexpr size_t kMaxQueue = 1024;
Receiver() = delete;
Receiver(const std::shared_ptr<const Thread::Lambda::ImmData>& imm) noexcept :
nf7::Lambda(nullptr), imm_(imm) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>&) noexcept override {
values_.emplace_back(idx, std::move(v));
if (values_.size() > kMaxQueue) {
values_.pop_front();
}
std::unique_lock<std::mutex> k(mtx_);
ResumeIf();
}
// must be called on luajit thread
// Returns true and pushes results to Lua stack when a value is already queued.
bool Select(lua_State* L, const std::shared_ptr<Thread>& th, std::vector<size_t>&& indices) noexcept {
std::unique_lock<std::mutex> k(mtx_);
L_ = L;
th_ = th;
waiting_ = std::move(indices);
return ResumeIf(false);
}
private:
std::shared_ptr<const Thread::Lambda::ImmData> imm_;
std::deque<std::pair<size_t, Value>> values_;
std::mutex mtx_;
lua_State* L_;
std::shared_ptr<Thread> th_;
std::vector<size_t> waiting_;
// don't forget to lock mtx_
bool ResumeIf(bool yielded = true) noexcept;
};
Thread::Lambda::Lambda(const std::shared_ptr<Thread>& th, nf7::Node& n) noexcept :
th_(th),
imm_(new ImmData {n.input(), n.output()}),
recv_(new Receiver {imm_}),
la_(n.CreateLambda(th->lambdaOwner())) {
}
void Thread::Lambda::PushMeta(lua_State* L) noexcept {
if (luaL_newmetatable(L, kTypeName)) {
lua_createtable(L, 0, 0);
// Lambda:send(name or idx, value)
lua_pushcfunction(L, [](auto L) {
auto self = GetPtr(L, 1);
const auto idx = GetIndex(L, 2, self->imm_->in);
const auto val = luajit::CheckValue(L, 3);
auto th = self->GetThread(L);
th->env().ExecSub(th->ctx(), [self, th, L, idx, val = std::move(val)]() mutable {
self->la_->Handle(idx, std::move(val), self->recv_);
th->ExecResume(L);
});
th->ExpectYield(L);
return lua_yield(L, 0);
});
lua_setfield(L, -2, "send");
// Lambda:recv(handler)
lua_pushcfunction(L, [](auto L) {
auto self = GetPtr(L, 1);
std::vector<size_t> indices = {};
if (lua_istable(L, 2)) {
indices.resize(lua_objlen(L, 2));
for (size_t i = 0; i < indices.size(); ++i) {
lua_rawgeti(L, 2, static_cast<int>(i+1));
indices[i] = GetIndex(L, -1, self->imm_->out);
lua_pop(L, 1);
}
} else {
indices.push_back(GetIndex(L, 2, self->imm_->out));
}
auto th = self->GetThread(L);
if (self->recv_->Select(L, th, std::move(indices))) {
return 2;
} else {
th->ExpectYield(L);
return lua_yield(L, 0);
}
});
lua_setfield(L, -2, "recv");
lua_setfield(L, -2, "__index");
PushWeakPtrDeleter<Thread::Lambda>(L);
lua_setfield(L, -2, "__gc");
}
}
size_t Thread::Lambda::GetIndex(lua_State* L, int v, std::span<const std::string> names) {
if (lua_isstring(L, v)) {
const char* name = lua_tostring(L, v);
auto itr = std::find(names.begin(), names.end(), name);
if (itr == names.end()) {
luaL_error(L, "unknown input name: %s", name);
}
return static_cast<size_t>(std::distance(names.begin(), itr));
} else {
const auto idx = luaL_checkinteger(L, v);
if (idx < 0) {
luaL_error(L, "index is negative");
}
const auto uidx = static_cast<size_t>(idx);
if (uidx >= names.size()) {
luaL_error(L, "index is too large");
}
return uidx;
}
}
bool Thread::Lambda::Receiver::ResumeIf(bool yielded) noexcept {
if (!th_) return false;
for (auto p = values_.begin(); p < values_.end(); ++p) {
auto itr = std::find(waiting_.begin(), waiting_.end(), p->first);
if (itr == waiting_.end()) {
continue;
}
const auto self = shared_from_this();
auto v = imm_->out[*itr];
if (yielded) {
th_->ExecResume(L_, std::move(imm_->out[*itr]), p->second);
} else {
luajit::PushAll(L_, std::move(imm_->out[*itr]), p->second);
}
values_.erase(p);
waiting_ = {};
th_ = nullptr;
return true;
}
return false;
}
} // namespace nf7::luajit

View File

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

View File

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

20
common/mutable_memento.hh Normal file
View File

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

162
common/mutex.hh Normal file
View File

@@ -0,0 +1,162 @@
#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;
}
std::function<void()> onLock = [](){};
std::function<void()> onUnlock = [](){};
private:
nf7::Life<Mutex> life_;
bool ex_ = false;
std::weak_ptr<Sync> sync_;
struct Item final {
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
std::shared_ptr<nf7::Context> ctx;
bool ex;
};
std::deque<Item> pends_;
std::shared_ptr<Lock> TryAcquireLock_(
const std::shared_ptr<nf7::Context>& ctx, bool ex) noexcept {
auto sync = sync_.lock();
if (sync) {
if (ex_ || ex) return nullptr;
} else {
sync = std::make_shared<Sync>(*this);
ex_ = ex;
sync_ = sync;
}
return std::make_shared<Mutex::Lock>(ctx, sync);
}
};
class Mutex::Sync {
public:
friend nf7::Mutex;
Sync() = delete;
Sync(nf7::Mutex& mtx) noexcept : mtx_(mtx.life_) {
}
Sync(const Sync&) = delete;
Sync(Sync&&) = delete;
Sync& operator=(const Sync&) = delete;
Sync& operator=(Sync&&) = delete;
~Sync() noexcept {
if (mtx_) {
auto& pends = mtx_->pends_;
if (pends.size() > 0) {
auto item = std::move(pends.front());
pends.pop_front();
mtx_->ex_ = false;
mtx_->sync_ = {};
auto k = mtx_->TryAcquireLock_(item.ctx, item.ex);
assert(k);
item.pro.Return(std::move(k));
} else {
mtx_->onUnlock();
}
}
}
private:
nf7::Life<nf7::Mutex>::Ref mtx_;
};
class Mutex::Lock {
public:
Lock(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<Mutex::Sync>& sync) noexcept :
ctx_(ctx), sync_(sync) {
}
Lock(const Lock&) = default;
Lock(Lock&&) = default;
Lock& operator=(const Lock&) = default;
Lock& operator=(Lock&&) = default;
~Lock() noexcept {
// Ensure that the Sync's destructor is called on worker thread.
ctx_->env().ExecSub(
ctx_, [sync = std::move(sync_)]() mutable { sync = nullptr; });
}
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<Mutex::Sync> sync_;
};
template <typename T>
class Mutex::Resource {
public:
Resource() = delete;
Resource(const std::shared_ptr<Mutex::Lock>& k, T&& v) noexcept :
lock_(k), value_(std::move(v)) {
}
Resource(const std::shared_ptr<Mutex::Lock>& k, const T& v) noexcept :
Resource(k, T {v}) {
}
Resource(const Resource&) = default;
Resource(Resource&&) = default;
Resource& operator=(const Resource&) = default;
Resource& operator=(Resource&&) = default;
T& operator*() noexcept { return value_; }
const T& operator*() const noexcept { return value_; }
T* operator->() noexcept { return &value_; }
const T* operator->() const noexcept { return &value_; }
const std::shared_ptr<Mutex::Lock>& lock() const noexcept { return lock_; }
const T& value() const noexcept { return value_; }
private:
std::shared_ptr<Mutex::Lock> lock_;
T value_;
};
} // namespace nf7

View File

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

View File

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

54
common/nfile.hh Normal file
View File

@@ -0,0 +1,54 @@
#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

69
common/nfile_unix.cc Normal file
View File

@@ -0,0 +1,69 @@
#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

57
common/nfile_watcher.hh Normal file
View File

@@ -0,0 +1,57 @@
#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() = default;
NFileWatcher(const NFileWatcher&) = delete;
NFileWatcher(NFileWatcher&&) = delete;
NFileWatcher& operator=(const NFileWatcher&) = delete;
NFileWatcher& operator=(NFileWatcher&&) = delete;
void Watch(const std::filesystem::path& npath) noexcept {
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

80
common/nfile_win.cc Normal file
View File

@@ -0,0 +1,80 @@
#include "common/nfile.hh"
extern "C" {
#include <windows.h>
}
namespace nf7 {
void NFile::Init() {
DWORD acc = 0;
DWORD flags = 0;
if (flags_ & kRead) {
acc |= GENERIC_READ;
flags |= OPEN_EXISTING;
}
if (flags_ & kWrite) {
acc |= GENERIC_WRITE;
flags |= OPEN_ALWAYS;
}
HANDLE h = CreateFileA(
path_.string().c_str(),
acc, 0, nullptr, flags, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
throw NFile::Exception {"open failure"};
}
handle_ = reinterpret_cast<uintptr_t>(h);
}
NFile::~NFile() noexcept {
auto h = reinterpret_cast<HANDLE>(handle_);
if (!CloseHandle(h)) {
// ;(
}
}
size_t NFile::Read(size_t offset, uint8_t* buf, size_t size) {
const auto h = reinterpret_cast<HANDLE>(handle_);
LONG off_low = offset & 0xFFFFFFFF;
LONG off_high = offset >> 32;
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
throw NFile::Exception {"failed to set file pointer"};
}
DWORD ret;
if (!ReadFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
throw NFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NFile::Write(size_t offset, const uint8_t* buf, size_t size) {
const auto h = reinterpret_cast<HANDLE>(handle_);
LONG off_low = offset & 0xFFFFFFFF;
LONG off_high = offset >> 32;
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
throw NFile::Exception {"failed to set file pointer"};
}
DWORD ret;
if (!WriteFile(h, buf, static_cast<DWORD>(size), &ret, nullptr)) {
throw NFile::Exception {"read failure"};
}
return static_cast<size_t>(ret);
}
size_t NFile::Truncate(size_t size) {
const auto h = reinterpret_cast<HANDLE>(handle_);
LONG off_low = size & 0xFFFFFFFF;
LONG off_high = size >> 32;
if (INVALID_SET_FILE_POINTER == SetFilePointer(h, off_low, &off_high, FILE_BEGIN)) {
throw NFile::Exception {"failed to set file pointer"};
}
if (!SetEndOfFile(h)) {
throw NFile::Exception {"SetEndOfFile failure"};
}
return size;
}
} // namespace nf7

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
#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;
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
std::unique_lock<std::mutex> lk {mtx_};
if (names_.contains(in.name)) {
names_.clear();
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();
}
private:
std::mutex mtx_;
std::shared_ptr<nf7::Node::Lambda> target_;
std::vector<Pair> q_;
std::unordered_set<std::string> names_;
std::optional<nf7::Future<Pair>::Promise> pro_;
};
} // namespace nf7

View File

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

102
common/ring_buffer.hh Normal file
View File

@@ -0,0 +1,102 @@
#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

114
common/sequencer.hh Normal file
View File

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

View File

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

View File

@@ -2,6 +2,7 @@
#include <memory>
#include <optional>
#include <utility>
#include "nf7.hh"
@@ -16,7 +17,11 @@ class Task : public nf7::Context,
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;
@@ -33,45 +38,58 @@ class Task : public nf7::Context,
auto self() noexcept {
return std::enable_shared_from_this<Task<T>>::shared_from_this();
}
auto fu() noexcept { return *fu_; }
std::optional<Future>& fu() noexcept { return *fu_; }
protected:
virtual typename nf7::Future<T>::Coro Proc() noexcept = 0;
virtual Coro Proc() noexcept = 0;
private:
std::optional<typename nf7::Future<T>::Coro> coro_;
std::optional<nf7::Future<T>> fu_;
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(const std::shared_ptr<Task<T>>& ctx) noexcept : ctx_(ctx) {
}
~Holder() noexcept {
Abort();
}
Holder(const Holder&) = delete;
Holder(Holder&& src) noexcept = default;
Holder(Holder&&) = delete;
Holder& operator=(const Holder&) = delete;
Holder& operator=(Holder&& src) noexcept {
if (this != &src) {
Abort();
ctx_ = std::move(src.ctx_);
}
return *this;
}
Holder& operator=(Holder&&) = delete;
bool CleanUp() noexcept {
return !!std::exchange(fu_, std::nullopt);
}
void Abort() noexcept {
if (auto ctx = ctx_.lock()) ctx->Abort();
ctx_ = {};
if (auto task = task_.lock()) {
task->Abort();
}
}
std::shared_ptr<Task<T>> lock() const noexcept { return ctx_.lock(); }
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>> ctx_;
std::weak_ptr<Task<T>> task_;
std::optional<nf7::Future<T>> fu_;
};
} // namespace nf7

View File

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

79
common/timed_queue.hh Normal file
View File

@@ -0,0 +1,79 @@
#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

23
common/util_algorithm.hh Normal file
View File

@@ -0,0 +1,23 @@
#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

78
common/util_string.hh Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <functional>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace nf7::util {
inline std::string_view Trim(
std::string_view str,
const std::function<bool(char)>& func = [](auto c) { return std::isspace(c); }) noexcept {
while (!str.empty() && func(str.front())) {
str.remove_prefix(1);
}
while (!str.empty() && func(str.back())) {
str.remove_suffix(1);
}
return str;
}
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
std::string_view ret;
while (ret.empty() && i < str.size()) {
auto j = str.find(c, i);
if (j == std::string::npos) j = str.size();
ret = str.substr(i, j-i);
i = j+1;
}
if (ret.empty()) return std::nullopt;
return ret;
}
inline void SplitAndAppend(std::vector<std::string>& dst, std::string_view src, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(src, c, itr)) {
dst.emplace_back(*term);
}
}
inline void JoinAndAppend(std::string& dst, std::span<const std::string> src, char c = '\n') noexcept {
for (auto& str : src) {
dst += str;
dst += c;
}
}
inline std::optional<std::string_view> SplitAndValidate(
std::string_view v,
const std::function<bool(std::string_view)> validator, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(v, c, itr)) {
if (validator(*term)) {
return term;
}
}
return std::nullopt;
}
inline std::optional<std::string_view> SplitAndValidate(
std::string_view v,
const std::function<void(std::string_view)> validator, char c = '\n') noexcept {
size_t itr = 0;
while (auto term = IterateTerms(v, c, itr)) {
try {
validator(*term);
} catch (nf7::Exception&) {
return term;
}
}
return std::nullopt;
}
} // namespace nf7::util

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
@@ -61,29 +62,26 @@ class Value {
Value& operator=(Scalar v) noexcept { value_ = v; return *this; }
Value(Boolean v) noexcept : value_(v) { }
Value& operator=(Boolean v) noexcept { value_ = v; return *this; }
Value(std::string_view v) noexcept : value_(std::string {v}) { }
Value& operator=(std::string_view v) noexcept { value_ = std::string(v); return *this; }
Value(String&& v) noexcept : value_(std::move(v)) { }
Value& operator=(String&& v) noexcept { value_ = std::move(v); return *this; }
Value(const Vector& v) noexcept { value_ = v; }
Value& operator=(const Vector& v) noexcept { value_ = v; return *this; }
Value(Vector&& v) noexcept { value_ = std::move(v); }
Value& operator=(Vector&& v) noexcept { value_ = std::move(v); return *this; }
Value(std::vector<uint8_t>&& v) noexcept { value_ = std::make_shared<std::vector<uint8_t>>(std::move(v)); }
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) { }
Value& operator=(const Tuple& v) noexcept { value_ = v; return *this; }
Value(Tuple&& v) noexcept : value_(std::move(v)) { }
Value& operator=(Tuple&& v) noexcept { value_ = std::move(v); return *this; }
Value(std::vector<TuplePair>&& p) noexcept { value_ = std::make_shared<std::vector<TuplePair>>(std::move(p)); }
Value(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 {
std::vector<TuplePair> pairs;
pairs.reserve(v.size());
std::transform(v.begin(), v.end(), std::back_inserter(pairs),
[](auto& x) { return TuplePair {"", std::move(x)}; });
value_ = std::make_shared<std::vector<TuplePair>>(std::move(pairs));
}
Value(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());
@@ -92,6 +90,7 @@ class Value {
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)) { }
@@ -106,18 +105,57 @@ class Value {
bool isInteger() const noexcept { return std::holds_alternative<Integer>(value_); }
bool isScalar() const noexcept { return std::holds_alternative<Scalar>(value_); }
bool isString() const noexcept { return std::holds_alternative<String>(value_); }
bool isVector() const noexcept { return std::holds_alternative<Vector>(value_); }
bool isTuple() const noexcept { return std::holds_alternative<Tuple>(value_); }
bool 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<Vector>(); }
const ConstTuple tuple() const { return get<Tuple>(); }
const ConstVector& vector() const { return get<ConstVector>(); }
const ConstTuple& tuple() const { return get<ConstTuple>(); }
const DataPtr& data() const { return get<DataPtr>(); }
// 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:
@@ -130,31 +168,36 @@ class Value {
return itr < tup.end()? itr->second:
throw IncompatibleException("unknown tuple field: "+std::string {name});
}
template <typename T>
std::shared_ptr<T> data() const {
if (auto ptr = std::dynamic_pointer_cast<T>(data())) return ptr;
throw IncompatibleException("data pointer downcast failure");
Value tupleOr(auto idx, const Value& v) const noexcept {
try {
return tuple(idx);
} catch (nf7::Exception&) {
return v;
}
}
Integer& integer() { return get<Integer>(); }
Boolean& boolean() { return get<Boolean>(); }
Scalar& scalar() { return get<Scalar>(); }
String& string() { return get<String>(); }
Vector vectorUniq() { return getUniq<Vector>(); }
Tuple tupleUniq() { return getUniq<Tuple>(); }
// 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()(Vector) noexcept { return "vector"; }
auto operator()(Tuple) noexcept { return "tuple"; }
auto operator()(DataPtr) noexcept { return "data"; }
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 Visit(Visitor{});
}
@@ -166,34 +209,53 @@ class Value {
}
private:
std::variant<Pulse, Boolean, Integer, Scalar, String, Vector, Tuple, DataPtr> value_;
std::variant<Pulse, Boolean, Integer, Scalar, String, ConstVector, ConstTuple, DataPtr> value_;
template <typename T>
const T& get() const
try {
return std::get<T>(value_);
} catch (std::bad_variant_access&) {
throw IncompatibleException(
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
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&) {
throw IncompatibleException(
std::string{"expected "}+typeid(T).name()+" but it's "+typeName());
std::stringstream st;
st << "expected " << typeid(T).name() << " but it's " << typeName();
throw IncompatibleException(st.str());
}
template <typename T>
T getUniq() {
std::shared_ptr<typename std::remove_const<typename T::element_type>::type> getUniq() {
auto v = std::move(get<T>());
if (v.use_count() == 1) {
return v;
return std::const_pointer_cast<typename std::remove_const<typename T::element_type>::type>(v);
} else {
return std::make_shared<typename T::element_type>(*v);
return std::make_shared<typename std::remove_const<typename T::element_type>::type>(*v);
}
}
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 {
@@ -244,6 +306,22 @@ struct serializer<
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<
@@ -263,6 +341,26 @@ struct serializer<
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<

View File

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

View File

@@ -1,86 +0,0 @@
#pragma once
#include <cassert>
#include <miniaudio.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
ma_device_type> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const ma_device_type& t) {
switch (t) {
case ma_device_type_playback:
ar("playback");
break;
case ma_device_type_capture:
ar("capture");
break;
default:
assert(false);
}
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, ma_device_type& t) {
std::string v;
ar(v);
if (v == "playback") {
t = ma_device_type_playback;
} else if (v == "capture") {
t = ma_device_type_capture;
} else {
throw nf7::DeserializeException("unknown device type");
}
return ar;
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
ma_device_config> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const ma_device_config& v) {
serialize(ar, v);
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, ma_device_config& v) {
serialize(ar, v);
if (v.sampleRate == 0) {
throw nf7::DeserializeException("invalid sample rate");
}
return ar;
}
private:
static void serialize(auto& ar, auto& v) {
ar(v.deviceType);
ar(v.sampleRate);
if (v.deviceType == ma_device_type_playback) {
ar(v.playback.format);
ar(v.playback.channels);
} else if (v.deviceType == ma_device_type_capture) {
ar(v.capture.format);
ar(v.capture.channels);
} else {
assert(false);
}
}
};
} // namespace yas::detail

42
common/yas_enum.hh Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <string_view>
#include <magic_enum.hpp>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
namespace nf7 {
template <typename T>
struct EnumSerializer {
public:
static auto& save(auto& ar, auto t) {
ar(magic_enum::enum_name(t));
return ar;
}
static auto& load(auto& ar, auto& t) {
std::string v;
ar(v);
if (auto ot = magic_enum::enum_cast<T>(v)) {
t = *ot;
} else {
throw nf7::DeserializeException {"unknown enum: "+v};
}
return ar;
}
};
#define NF7_YAS_DEFINE_ENUM_SERIALIZER(T) \
template <size_t F> \
struct serializer< \
yas::detail::type_prop::is_enum, \
yas::detail::ser_case::use_internal_serializer, \
F, T> : nf7::EnumSerializer<T> { \
}
} // namespace nf7

View File

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

25
common/yas_std_variant.hh Normal file
View File

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

View File

@@ -1,4 +1,6 @@
#include <algorithm>
#include <atomic>
#include <cinttypes>
#include <memory>
#include <imgui.h>
@@ -19,25 +21,39 @@ namespace {
class AudioContext final : public nf7::File, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<AudioContext> kType = {"Audio/Context", {"DirItem",}};
static inline const nf7::GenericTypeInfo<AudioContext> kType = {
"Audio/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives miniaudio context.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::audio::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"there's no merit to use multiple contexts");
ImGui::Bullet(); ImGui::TextUnformatted(
"the context remains alive after file deletion until unused");
}
class Queue;
AudioContext(Env&) noexcept;
AudioContext(Env& env) noexcept :
nf7::File(kType, env),
nf7::DirItem(DirItem::kMenu | DirItem::kTooltip),
q_(std::make_shared<AudioContext::Queue>(*this)) {
}
AudioContext(Env& env, Deserializer&) : AudioContext(env) {
AudioContext(nf7::Deserializer& ar) noexcept : AudioContext(ar.env()) {
}
void Serialize(Serializer&) const noexcept override {
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<AudioContext>(env);
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
static void UpdateDeviceListMenu(ma_device_info*, ma_uint32) noexcept;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
}
@@ -46,173 +62,154 @@ class AudioContext final : public nf7::File, public nf7::DirItem {
std::shared_ptr<Queue> q_;
const char* popup_ = nullptr;
// for device list popup
struct DeviceList {
std::atomic<bool> working;
bool success;
ma_device_info* play;
ma_uint32 play_n;
ma_device_info* cap;
ma_uint32 cap_n;
};
std::shared_ptr<DeviceList> devlist_;
void UpdateDeviceList(const ma_device_info*, size_t n) noexcept;
};
class AudioContext::Queue final : public nf7::audio::Queue,
public std::enable_shared_from_this<AudioContext::Queue> {
public:
struct Runner final {
Runner(Queue& owner) noexcept : owner_(&owner) {
}
void operator()(Task&& t) {
t(owner_->ctx_.get());
}
private:
Queue* const owner_;
};
using Thread = nf7::Thread<Runner, Task>;
enum State {
kInitializing,
kInitial,
kReady,
kBroken,
};
struct ThreadData {
public:
std::atomic<State> state = kInitial;
ma_context ctx;
};
struct Runner {
public:
Runner(const std::shared_ptr<ThreadData>& tdata) noexcept : tdata_(tdata) {
}
void operator()(Task&& t) {
if (tdata_->state != kBroken) {
t(&tdata_->ctx);
}
}
private:
std::shared_ptr<ThreadData> tdata_;
};
using Thread = nf7::Thread<Runner, Task>;
Queue() = delete;
Queue(Env& env) : env_(&env), th_(std::make_shared<Thread>(env, Runner {*this})) {
Queue(AudioContext& f) noexcept :
env_(&f.env()),
tdata_(std::make_shared<ThreadData>()),
th_(std::make_shared<Thread>(f, Runner {tdata_})) {
auto ctx = std::make_shared<nf7::GenericContext>(f.env(), 0, "creating ma_context");
th_->Push(ctx, [tdata = tdata_](auto) {
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, &tdata->ctx)) {
tdata->state = kReady;
} else {
tdata->state = kBroken;
}
});
}
~Queue() noexcept {
th_->Push(
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
[ctx = std::move(ctx_)](auto) { if (ctx) ma_context_uninit(ctx.get()); }
);
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
[](auto ma) { ma_context_uninit(ma); }
);
}
Queue(const Queue&) = delete;
Queue(Queue&&) = delete;
Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete;
void Init(Env& env) noexcept {
th_->Push(
std::make_shared<nf7::GenericContext>(env, 0, "creating ma_context"),
[this, self = shared_from_this()](auto) {
auto ctx = std::make_shared<ma_context>();
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, ctx.get())) {
ctx_ = std::move(ctx);
state_ = kReady;
} else {
state_ = kBroken;
}
});
}
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task));
}
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
State state() const noexcept { return state_; }
State state() const noexcept { return tdata_->state; }
size_t tasksDone() const noexcept { return th_->tasksDone(); }
// thread-safe
ma_context* ctx() const noexcept {
return state() == kReady? &tdata_->ctx: nullptr;
}
private:
Env* const env_;
std::shared_ptr<ThreadData> tdata_;
std::shared_ptr<Thread> th_;
std::atomic<State> state_ = kInitializing;
std::shared_ptr<ma_context> ctx_;
};
AudioContext::AudioContext(Env& env) noexcept :
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
q_(std::make_shared<Queue>(env)) {
q_->Init(env);
}
void AudioContext::Update() noexcept {
if (auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("DeviceList")) {
auto& p = devlist_;
ImGui::TextUnformatted("Audio/Context: device list");
if (ImGui::IsWindowAppearing()) {
if (!p) {
p = std::make_shared<DeviceList>();
}
p->working = true;
q_->Push(
std::make_shared<nf7::GenericContext>(*this, "fetching audio device list"),
[p](auto ctx) {
p->success = false;
if (ctx) {
const auto ret = ma_context_get_devices(
ctx, &p->play, &p->play_n, &p->cap, &p->cap_n);
p->success = ret == MA_SUCCESS;
}
p->working = false;
});
}
ImGui::Indent();
if (p->working) {
ImGui::TextUnformatted("fetching audio devices... :)");
} else {
if (p->success) {
ImGui::TextUnformatted("playback:");
ImGui::Indent();
UpdateDeviceList(p->play, p->play_n);
ImGui::Unindent();
ImGui::TextUnformatted("capture:");
ImGui::Indent();
UpdateDeviceList(p->cap, p->cap_n);
ImGui::Unindent();
} else {
ImGui::TextUnformatted("failed to fetch devices X(");
}
}
ImGui::Unindent();
ImGui::EndPopup();
}
}
void AudioContext::UpdateMenu() noexcept {
if (ImGui::MenuItem("display available devices")) {
popup_ = "DeviceList";
ma_device_info* pbs;
ma_uint32 pbn;
ma_device_info* cps;
ma_uint32 cpn;
if (ImGui::BeginMenu("playback devices")) {
auto ma = q_->ctx();
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
UpdateDeviceListMenu(pbs, pbn);
} else {
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("capture devices")) {
auto ma = q_->ctx();
if (MA_SUCCESS == ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
UpdateDeviceListMenu(cps, cpn);
} else {
ImGui::MenuItem("fetch failure... ;(", nullptr, false, false);
}
ImGui::EndMenu();
}
}
void AudioContext::UpdateDeviceListMenu(ma_device_info* ptr, ma_uint32 n) noexcept {
for (ma_uint32 i = 0; i < n; ++i) {
const auto name = std::to_string(i) + ": " + ptr[i].name;
if (ImGui::MenuItem(name.c_str())) {
ImGui::SetClipboardText(ptr[i].name);
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("index : %" PRIu32, i);
ImGui::Text("name : %s", ptr[i].name);
ImGui::TextDisabled(" click to copy the name");
ImGui::Text("default: %s", ptr[i].isDefault? "yes": "no");
ImGui::TextUnformatted("native formats:");
const auto fmtn = std::min(ptr[i].nativeDataFormatCount, ma_uint32 {5});
for (ma_uint32 j = 0; j < fmtn; ++j) {
const auto& d = ptr[i].nativeDataFormats[j];
const char* fmt =
d.format == ma_format_u8? "u8":
d.format == ma_format_s16? "s16":
d.format == ma_format_s24? "s24":
d.format == ma_format_s32? "s32":
d.format == ma_format_f32? "f32":
"unknown";
ImGui::Bullet();
ImGui::Text("%s / %" PRIu32 " ch / %" PRIu32 " Hz", fmt, d.channels, d.sampleRate);
}
if (ptr[i].nativeDataFormatCount > fmtn) {
ImGui::Bullet(); ImGui::TextDisabled("etc...");
}
if (fmtn == 0) {
ImGui::Bullet(); ImGui::TextDisabled("(nothing)");
}
ImGui::EndTooltip();
}
}
}
void AudioContext::UpdateTooltip() noexcept {
const auto state = q_->state();
const char* state_str =
state == Queue::kInitializing? "initializing":
state == Queue::kReady ? "ready":
state == Queue::kBroken ? "broken": "unknown";
state == Queue::kInitial? "initializing":
state == Queue::kReady ? "ready":
state == Queue::kBroken ? "broken": "unknown";
ImGui::Text("state: %s", state_str);
}
void AudioContext::UpdateDeviceList(const ma_device_info* p, size_t n) noexcept {
for (size_t i = 0; i < n; ++i) {
const auto& info = p[i];
const auto name = std::to_string(i) + ": " + info.name;
ImGui::Selectable(name.c_str(), false, ImGuiSelectableFlags_DontClosePopups);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("index : %zu", i);
ImGui::Text("name : %s", info.name);
ImGui::Text("default : %s", info.isDefault? "true": "false");
ImGui::EndTooltip();
}
}
}
}
} // namespace nf7

500
file/audio_device.cc Normal file
View File

@@ -0,0 +1,500 @@
#include <cinttypes>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <typeinfo>
#include <utility>
#include <vector>
#include <imgui.h>
#include <magic_enum.hpp>
#include <miniaudio.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
#include "common/audio_queue.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui_config.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/ring_buffer.hh"
#include "common/value.hh"
#include "common/yas_enum.hh"
#include "common/yas_nf7.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Device final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Device> kType = {
"Audio/Device", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Provides a ring buffer to send/receive PCM samples.");
}
class Instance;
class Lambda;
enum class Mode {
Playback, Capture,
};
static ma_device_type FromMode(Mode m) {
return
m == Mode::Playback? ma_device_type_playback:
m == Mode::Capture ? ma_device_type_capture:
throw 0;
}
// the least 4 bits represent size of the type
enum class Format {
U8 = 0x11, S16 = 0x22, S32 = 0x24, F32 = 0x34,
};
static ma_format FromFormat(Format f) {
return
f == Format::U8 ? ma_format_u8 :
f == Format::S16? ma_format_s16:
f == Format::S32? ma_format_s32:
f == Format::F32? ma_format_f32:
throw 0;
}
struct Data {
Data() noexcept { }
std::string Stringify() const noexcept;
void Parse(const std::string&);
void serialize(auto& ar) {
ar(ctxpath, mode, devname, fmt, srate, ch);
}
nf7::File::Path ctxpath = {"$", "_audio"};
Mode mode = Mode::Playback;
std::string devname = "";
Format fmt = Format::F32;
uint32_t srate = 48000;
uint32_t ch = 1;
uint64_t ring_size = 48000*3;
};
Device(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&log_}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), mem_(std::move(data), *this) {
mem_.onCommit = mem_.onRestore = [this]() { cache_ = std::nullopt; };
}
Device(nf7::Deserializer& ar) : Device(ar.env()) {
ar(mem_.data());
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Device>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
static const std::vector<std::string> kInputs = {"info", "mix", "peek"};
return kInputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
static const std::vector<std::string> kOutputs = {"result"};
return kOutputs;
}
nf7::Future<std::shared_ptr<Instance>> Build() noexcept;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<Device> life_;
nf7::LoggerRef log_;
nf7::GenericMemento<Data> mem_;
std::optional<nf7::Future<std::shared_ptr<Instance>>> cache_;
static const ma_device_id* ChooseDevice(
ma_device_info* ptr, ma_uint32 n, std::string_view name, std::string& result) {
for (ma_uint32 i = 0; i < n; ++i) {
const auto& d = ptr[i];
bool choose = false;
if (name.empty()) {
if (d.isDefault) {
choose = true;
}
} else {
if (std::string_view::npos != std::string_view {d.name}.find(name)) {
choose = true;
}
}
if (choose) {
result = d.name;
return &d.id;
}
}
throw nf7::Exception {"no device found"};
}
};
class Device::Instance final {
public:
// must be called on audio thread
Instance(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::audio::Queue>& aq,
ma_context* ma, const Data& d) :
ctx_(ctx), aq_(aq), data_(d),
sdata_(std::make_shared<SharedData>(
magic_enum::enum_integer(d.fmt) & 0xF, d.ring_size)) {
// get device list
ma_device_info* pbs;
ma_uint32 pbn;
ma_device_info* cps;
ma_uint32 cpn;
if (MA_SUCCESS != ma_context_get_devices(ma, &pbs, &pbn, &cps, &cpn)) {
throw nf7::Exception {"failed to get device list"};
}
// construct device config
ma_device_config cfg = ma_device_config_init(FromMode(d.mode));
switch (d.mode) {
case Mode::Playback:
cfg.dataCallback = PlaybackCallback;
cfg.playback.pDeviceID = ChooseDevice(pbs, pbn, d.devname, devname_);
cfg.playback.format = FromFormat(d.fmt);
cfg.playback.channels = d.ch;
break;
case Mode::Capture:
cfg.dataCallback = CaptureCallback;
cfg.capture.pDeviceID = ChooseDevice(cps, cpn, d.devname, devname_);
cfg.capture.format = FromFormat(d.fmt);
cfg.capture.channels = d.ch;
break;
}
cfg.sampleRate = d.srate;
cfg.pUserData = sdata_.get();
if (MA_SUCCESS != ma_device_init(ma, &cfg, &sdata_->dev)) {
throw nf7::Exception {"device init failure"};
}
if (MA_SUCCESS != ma_device_start(&sdata_->dev)) {
ma_device_uninit(&sdata_->dev);
throw nf7::Exception {"device start failure"};
}
}
~Instance() noexcept {
aq_->Push(ctx_, [sdata = sdata_](auto) {
ma_device_uninit(&sdata->dev);
});
}
Instance(const Instance&) = delete;
Instance(Instance&&) = delete;
Instance& operator=(const Instance&) = delete;
Instance& operator=(Instance&&) = delete;
std::mutex& mtx() const noexcept { return sdata_->mtx; }
nf7::RingBuffer& ring() noexcept { return sdata_->ring; }
const nf7::RingBuffer& ring() const noexcept { return sdata_->ring; }
const std::string& devname() const noexcept { return devname_; }
Data data() const noexcept { return data_; }
private:
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::audio::Queue> aq_;
std::string devname_;
Data data_;
struct SharedData {
SharedData(uint64_t a, uint64_t b) noexcept : ring(a, b) {
}
mutable std::mutex mtx;
nf7::RingBuffer ring;
ma_device dev;
};
std::shared_ptr<SharedData> sdata_;
static void PlaybackCallback(
ma_device* dev, void* out, const void*, ma_uint32 n) noexcept {
auto& sdata = *reinterpret_cast<SharedData*>(dev->pUserData);
std::unique_lock<std::mutex> _(sdata.mtx);
sdata.ring.Take(reinterpret_cast<uint8_t*>(out), n);
}
static void CaptureCallback(
ma_device* dev, void*, const void* in, ma_uint32 n) noexcept {
auto& sdata = *reinterpret_cast<SharedData*>(dev->pUserData);
std::unique_lock<std::mutex> _(sdata.mtx);
sdata.ring.Write(reinterpret_cast<const uint8_t*>(in), n);
}
};
class Device::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Device::Lambda> {
public:
Lambda(Device& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override {
if (!f_) return;
f_->Build().
ThenIf(shared_from_this(), [this, in](auto& inst) {
if (!f_) return;
try {
Exec(in, inst);
} catch (nf7::Exception& e) {
f_->log_.Error(e);
}
}).
Catch<nf7::Exception>(shared_from_this(), [this](auto&) {
if (f_) {
f_->log_.Warn("skip execution because of device init failure");
}
});
}
private:
nf7::Life<Device>::Ref f_;
std::weak_ptr<Instance> last_inst_;
uint64_t time_ = 0;
void Exec(const nf7::Node::Lambda::Msg& in,
const std::shared_ptr<Instance>& inst) {
const bool reset = last_inst_.expired();
last_inst_ = inst;
const auto& data = inst->data();
auto& ring = inst->ring();
if (in.name == "info") {
std::vector<nf7::Value::TuplePair> tup {
{"format", magic_enum::enum_name(data.fmt)},
{"srate", static_cast<nf7::Value::Integer>(data.srate)},
{"ch", static_cast<nf7::Value::Integer>(data.ch)},
};
in.sender->Handle("result", std::move(tup), shared_from_this());
} else if (in.name == "mix") {
if (data.mode != Mode::Playback) {
throw nf7::Exception {"device mode is not playback"};
}
const auto& vec = *in.value.vector();
std::unique_lock<std::mutex> lock(inst->mtx());
if (reset) time_ = ring.cur();
const auto ptime = time_;
const auto Mix = [&]<typename T>() {
time_ = ring.Mix(
time_, reinterpret_cast<const T*>(vec.data()), vec.size()/sizeof(T));
};
switch (data.fmt) {
case Format::U8 : Mix.operator()<uint8_t>(); break;
case Format::S16: Mix.operator()<int16_t>(); break;
case Format::S32: Mix.operator()<int32_t>(); break;
case Format::F32: Mix.operator()<float>(); break;
}
lock.unlock();
const auto wrote = (time_-ptime) / data.ch;
in.sender->Handle(
"result", static_cast<nf7::Value::Integer>(wrote), shared_from_this());
} else if (in.name == "peek") {
if (data.mode != Mode::Playback) {
throw nf7::Exception {"device mode is not capture"};
}
const auto expect_read = std::min(
ring.bufn(), in.value.integer<uint64_t>()*data.ch);
std::vector<uint8_t> buf(expect_read*ring.unit());
std::unique_lock<std::mutex> lock(inst->mtx());
if (reset) time_ = ring.cur();
const auto ptime = time_;
time_ = ring.Peek(time_, buf.data(), expect_read);
lock.unlock();
const auto read = time_ - ptime;
in.sender->Handle(
"result", static_cast<nf7::Value::Integer>(read), shared_from_this());
} else {
throw nf7::Exception {"unknown command type: "+in.name};
}
}
};
std::shared_ptr<nf7::Node::Lambda> Device::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Device::Lambda>(*this, parent);
}
nf7::Future<std::shared_ptr<Device::Instance>> Device::Build() noexcept
try {
if (cache_) return *cache_;
auto ctx = std::make_shared<
nf7::GenericContext>(*this, "audio device instance builder");
auto aq =
ResolveOrThrow(mem_->ctxpath).
interfaceOrThrow<nf7::audio::Queue>().self();
nf7::Future<std::shared_ptr<Device::Instance>>::Promise pro;
aq->Push(ctx, [ctx, aq, pro, d = mem_.data()](auto ma) mutable {
pro.Wrap([&]() { return std::make_shared<Instance>(ctx, aq, ma, d); });
});
cache_ = pro.future().
Catch<nf7::Exception>([f = nf7::Life<Device>::Ref {life_}](auto& e) {
if (f) f->log_.Error(e);
});
return *cache_;
} catch (nf7::Exception& e) {
log_.Error(e);
return {std::current_exception()};
}
void Device::UpdateMenu() noexcept {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
if (ImGui::MenuItem("build")) {
Build();
}
}
void Device::UpdateTooltip() noexcept {
ImGui::Text("format : %s / %" PRIu32 " ch / %" PRIu32 " Hz",
magic_enum::enum_name(mem_->fmt).data(), mem_->ch, mem_->srate);
if (!cache_) {
ImGui::TextUnformatted("status : unused");
} else if (cache_->yet()) {
ImGui::TextUnformatted("status : initializing");
} else if (cache_->done()) {
auto& inst = *cache_->value();
ImGui::TextUnformatted("status : running");
ImGui::Text("devname: %s", inst.devname().c_str());
} else if (cache_->error()) {
ImGui::TextUnformatted("status : invalid");
try {
cache_->value();
} catch (nf7::Exception& e) {
ImGui::Text("msg : %s", e.msg().c_str());
}
}
}
std::string Device::Data::Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "ctxpath";
st << YAML::Value << ctxpath.Stringify();
st << YAML::Key << "mode";
st << YAML::Value << std::string {magic_enum::enum_name(mode)};
st << YAML::Key << "devname";
st << YAML::Value << devname << YAML::Comment("leave empty to choose default one");
st << YAML::Key << "format";
st << YAML::Value << std::string {magic_enum::enum_name(fmt)};
st << YAML::Key << "srate";
st << YAML::Value << srate;
st << YAML::Key << "ch";
st << YAML::Value << ch;
st << YAML::Key << "ring_size";
st << YAML::Value << ring_size;
st << YAML::EndMap;
return {st.c_str(), st.size()};
}
void Device::Data::Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
Data d;
d.ctxpath = nf7::File::Path::Parse(yaml["ctxpath"].as<std::string>());
d.mode = magic_enum::enum_cast<Mode>(yaml["mode"].as<std::string>()).value();
d.devname = yaml["devname"].as<std::string>();
d.fmt = magic_enum::enum_cast<Format>(yaml["format"].as<std::string>()).value();
d.srate = yaml["srate"].as<uint32_t>();
d.ch = yaml["ch"].as<uint32_t>();
d.ring_size = yaml["ring_size"].as<uint64_t>();
if (d.srate > d.ring_size) {
throw nf7::Exception {"ring size is too small (must be srate or more)"};
}
if (d.srate*10 < d.ring_size) {
throw nf7::Exception {"ring size is too large (must be srate*10 or less)"};
}
*this = std::move(d);
} catch (std::bad_optional_access&) {
throw nf7::Exception {"invalid enum"};
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
}
} // namespace nf7
namespace yas::detail {
template <size_t F>
struct serializer<
yas::detail::type_prop::is_enum,
yas::detail::ser_case::use_internal_serializer,
F, nf7::Device::Mode> :
nf7::EnumSerializer<nf7::Device::Mode> {
};
template <size_t F>
struct serializer<
yas::detail::type_prop::is_enum,
yas::detail::ser_case::use_internal_serializer,
F, nf7::Device::Format> :
nf7::EnumSerializer<nf7::Device::Format> {
};
} // namespace yas::detail

View File

@@ -1,706 +0,0 @@
#include <algorithm>
#include <cassert>
#include <cinttypes>
#include <cmath>
#include <cstdlib>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <imgui.h>
#include <miniaudio.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/variant.hpp>
#include "nf7.hh"
#include "common/audio_queue.hh"
#include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/lambda.hh"
#include "common/logger_ref.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_audio.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class IO final : public nf7::File, public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<IO> kType = {"Audio/IO", {"DirItem",}};
class Ring;
class PlaybackLambda;
class CaptureLambda;
using DeviceSelector = std::variant<size_t, std::string>;
struct DeviceSelectorVisitor;
static ma_device_config defaultConfig() noexcept {
ma_device_config cfg;
cfg = ma_device_config_init(ma_device_type_playback);
cfg.sampleRate = 480000;
cfg.playback.format = ma_format_f32;
cfg.playback.channels = 2;
cfg.capture.format = ma_format_f32;
cfg.capture.channels = 2;
return cfg;
}
IO(Env& env,
DeviceSelector&& sel = size_t{0},
const ma_device_config& cfg = defaultConfig()) noexcept :
File(kType, env), nf7::DirItem(DirItem::kMenu | DirItem::kTooltip),
data_(std::make_shared<Data>()),
selector_(std::move(sel)), cfg_(cfg) {
}
IO(Env& env, Deserializer& ar) : IO(env) {
ar(selector_, cfg_);
}
void Serialize(Serializer& ar) const noexcept override {
ar(selector_, cfg_);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<IO>(env, DeviceSelector {selector_}, cfg_);
}
std::shared_ptr<nf7::Lambda> CreateLambda(const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
void Handle(const Event&) noexcept override;
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateNode(Node::Editor&) noexcept override { }
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
}
private:
const char* popup_ = nullptr;
struct Data {
public:
Data() noexcept : ring(std::make_unique<Ring>()) {
}
nf7::LoggerRef log;
std::unique_ptr<Ring> ring;
std::shared_ptr<nf7::audio::Queue> aq;
std::optional<ma_device> dev;
std::atomic<size_t> busy = 0;
};
std::shared_ptr<Data> data_;
// persistent params
DeviceSelector selector_;
ma_device_config cfg_;
// ConfigPopup param
struct ConfigPopup final : std::enable_shared_from_this<ConfigPopup> {
ma_device_config cfg;
DeviceSelector selector;
std::atomic<bool> fetching = false;
ma_device_info* devs = nullptr;
ma_uint32 devs_n = 0;
void FetchDevs(File& f, const std::shared_ptr<nf7::audio::Queue>& aq) noexcept {
const auto mode = cfg.deviceType;
fetching = true;
aq->Push(
std::make_shared<nf7::GenericContext>(f, "fetching device list"),
[this, self = shared_from_this(), mode](auto ma) {
try {
auto [ptr, n] = IO::FetchDevs(ma, mode);
devs = ptr;
devs_n = static_cast<ma_uint32>(n);
} catch (nf7::Exception&) {
devs = nullptr;
devs_n = 0;
}
fetching = false;
});
}
};
std::shared_ptr<ConfigPopup> config_popup_;
void InitDev() noexcept;
void DeinitDev() noexcept;
void BuildNode() noexcept;
static std::pair<ma_device_info*, size_t> FetchDevs(ma_context* ctx, ma_device_type mode) {
ma_device_info* devs = nullptr;
ma_uint32 num = 0;
const auto ret =
mode == ma_device_type_playback?
ma_context_get_devices(ctx, &devs, &num, nullptr, nullptr):
mode == ma_device_type_capture?
ma_context_get_devices(ctx, nullptr, nullptr, &devs, &num):
throw nf7::Exception("unknown mode");
if (MA_SUCCESS != ret) {
throw nf7::Exception("failed to get device list");
}
return {devs, num};
}
static auto& GetChannels(auto& cfg) noexcept {
switch (cfg.deviceType) {
case ma_device_type_playback:
return cfg.playback.channels;
case ma_device_type_capture:
return cfg.capture.channels;
default:
std::abort();
}
}
static std::string StringifyPreset(const auto& p) noexcept {
std::stringstream st;
st << "f32, " << p.sampleRate << "Hz, " << p.channels << " ch";
return st.str();
}
static std::vector<float> GenerateSineWave(uint32_t srate, uint32_t ch) noexcept {
std::vector<float> ret;
ret.resize(srate*ch);
for (size_t i = 0; i < srate; ++i) {
const double t = static_cast<double>(i)/static_cast<double>(srate);
const float v = static_cast<float>(sin(t*200*2*3.14));
for (size_t j = 0; j < ch; ++j) {
ret[i*ch + j] = v;
}
}
return ret;
}
static bool UpdateModeSelector(ma_device_type*) noexcept;
static const ma_device_info* UpdateDeviceSelector(DeviceSelector*, ma_device_info*, size_t) noexcept;
static void UpdatePresetSelector(ma_device_config*, const ma_device_info*) noexcept;
nf7::Value infoTuple() const noexcept {
return nf7::Value {std::vector<nf7::Value::TuplePair> {
{"sampleRate", {static_cast<nf7::Value::Integer>(cfg_.sampleRate)}},
{"channels", {static_cast<nf7::Value::Integer>(GetChannels(cfg_))}},
}};
}
};
class IO::Ring final {
public:
static constexpr auto kDur = 3000; /* msecs */
Ring() noexcept {
Reset(1, 1);
}
Ring(const Ring&) = delete;
Ring(Ring&&) = delete;
Ring& operator=(const Ring&) = delete;
Ring& operator=(Ring&&) = delete;
void Reset(uint32_t srate, uint32_t ch) noexcept {
std::unique_lock<std::mutex> k(mtx_);
time_begin_ = time_;
cursor_ = 0;
buf_.clear();
buf_.resize(kDur*srate*ch/1000);
}
// for playback mode: mix samples into this ring
uint64_t Mix(const float* ptr, size_t n, uint64_t time) noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (time < time_) time = time_;
if (time-time_ > buf_.size()) return time_+buf_.size();
const size_t vn = std::min(n, buf_.size());
const size_t offset = (time-time_begin_)%buf_.size();
for (size_t srci = 0, dsti = offset; srci < vn; ++srci, ++dsti) {
if (dsti >= buf_.size()) dsti = 0;
buf_[dsti] += ptr[srci];
}
return time+vn;
}
// for playback mode: consume samples in this ring
void Consume(float* dst, size_t n) noexcept {
std::unique_lock<std::mutex> k(mtx_);
for (size_t i = 0; i < n; ++i, ++cursor_) {
if (cursor_ >= buf_.size()) cursor_ = 0;
dst[i] = std::exchange(buf_[cursor_], 0.f);
}
time_ += n;
}
// for capture mode: append samples to this ring
void Append(const float* src, size_t n) noexcept {
std::unique_lock<std::mutex> k(mtx_);
const size_t vn = std::min(n, buf_.size());
for (size_t i = 0; i < vn; ++i, ++cursor_) {
if (cursor_ >= buf_.size()) cursor_ = 0;
buf_[cursor_] = src[i];
}
time_ += n;
}
// for capture mode: read samples
// actual samples are stored as float32 in dst
uint64_t Peek(std::vector<uint8_t>& dst, uint64_t ptime) noexcept {
std::unique_lock<std::mutex> k(mtx_);
const size_t vn = std::min(time_-ptime, buf_.size());
dst.resize(vn*sizeof(float));
float* dstp = reinterpret_cast<float*>(dst.data());
for (size_t i = 0, dsti = vn, srci = cursor_; i < vn; ++i) {
if (srci == 0) srci = buf_.size();
--dsti, --srci;
dstp[dsti] = buf_[srci];
}
return time_;
}
uint64_t time() const noexcept { return time_; }
private:
std::mutex mtx_;
uint32_t ch_;
size_t cursor_ = 0;
std::vector<float> buf_;
uint64_t time_begin_ = 0;
std::atomic<uint64_t> time_ = 0;
};
class IO::PlaybackLambda final : public nf7::Lambda,
public std::enable_shared_from_this<IO::PlaybackLambda> {
public:
static inline const std::vector<std::string> kInputs = {"get_info", "mix"};
static inline const std::vector<std::string> kOutputs = {"info", "mixed_size"};
enum {
kInGetInfo = 0,
kInSamples = 1,
kOutInfo = 0,
kOutSampleCount = 1,
};
PlaybackLambda() = delete;
PlaybackLambda(IO& f, const std::shared_ptr<Owner>& owner) noexcept :
Lambda(owner), data_(f.data_), info_(f.infoTuple()) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>& caller) noexcept override
try {
switch (idx) {
case kInGetInfo:
caller->Handle(kOutInfo, nf7::Value {info_}, shared_from_this());
break;
case kInSamples: {
const auto& vec = v.vector();
const auto ptr = reinterpret_cast<const float*>(vec->data());
const auto n = vec->size()/sizeof(float);
auto ptime = time_;
time_ = data_->ring->Mix(ptr, n, time_);
if (time_ < ptime) ptime = time_;
caller->Handle(kOutSampleCount, static_cast<nf7::Value::Integer>(time_-ptime), shared_from_this());
} break;
default:
throw nf7::Exception("got unknown input");
}
} catch (nf7::Exception& e) {
data_->log.Warn(e.msg());
}
private:
std::shared_ptr<Data> data_;
nf7::Value info_;
uint64_t time_ = 0;
};
class IO::CaptureLambda final : public nf7::Lambda,
std::enable_shared_from_this<IO::CaptureLambda> {
public:
static inline const std::vector<std::string> kInputs = {"get_info", "peek"};
static inline const std::vector<std::string> kOutputs = {"info", "samples"};
enum {
kInGetInfo = 0,
kInPeek = 1,
kOutInfo = 0,
kOutSamples = 1,
};
CaptureLambda() = delete;
CaptureLambda(IO& f, const std::shared_ptr<Owner>& owner) noexcept :
Lambda(owner), data_(f.data_), info_(f.infoTuple()) {
}
void Handle(size_t idx, nf7::Value&&, const std::shared_ptr<nf7::Lambda>& caller) noexcept override
try {
switch (idx) {
case kInGetInfo:
caller->Handle(kOutInfo, nf7::Value {info_}, shared_from_this());
break;
case kInPeek: {
std::vector<uint8_t> samples;
if (time_) {
time_ = data_->ring->Peek(samples, *time_);
} else {
time_ = data_->ring->time();
}
caller->Handle(kOutSamples, {std::move(samples)}, shared_from_this());
} break;
default:
throw nf7::Exception("got unknown input");
}
} catch (nf7::Exception& e) {
data_->log.Warn(e.msg());
}
private:
std::shared_ptr<Data> data_;
nf7::Value info_;
std::optional<uint64_t> time_;
};
std::shared_ptr<nf7::Lambda> IO::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
return std::make_shared<IO::PlaybackLambda>(*this, owner);
case ma_device_type_capture:
return std::make_shared<IO::CaptureLambda>(*this, owner);
default:
std::abort();
}
}
struct IO::DeviceSelectorVisitor final {
public:
DeviceSelectorVisitor() = delete;
DeviceSelectorVisitor(ma_device_info* info, size_t n) noexcept : info_(info), n_(n) {
}
DeviceSelectorVisitor(const DeviceSelectorVisitor&) = delete;
DeviceSelectorVisitor(DeviceSelectorVisitor&&) = delete;
DeviceSelectorVisitor& operator=(const DeviceSelectorVisitor&) = delete;
DeviceSelectorVisitor& operator=(DeviceSelectorVisitor&&) = delete;
ma_device_info* operator()(const size_t& idx) noexcept {
return idx < n_? &info_[idx]: nullptr;
}
ma_device_info* operator()(const std::string& name) noexcept {
for (size_t i = 0; i < n_; ++i) {
auto& d = info_[i];
if (name == d.name) return &d;
}
return nullptr;
}
private:
ma_device_info* info_;
size_t n_;
};
void IO::InitDev() noexcept {
if (!data_->aq) {
data_->log.Error("audio queue is missing");
return;
}
static const auto kPlaybackCallback = [](ma_device* dev, void* out, const void*, ma_uint32 n) {
auto& ring = *static_cast<Ring*>(dev->pUserData);
ring.Consume(static_cast<float*>(out), n*dev->playback.channels);
};
static const auto kCaptureCallback = [](ma_device* dev, void*, const void* in, ma_uint32 n) {
auto& ring = *static_cast<Ring*>(dev->pUserData);
ring.Append(static_cast<const float*>(in), n*dev->capture.channels);
};
++data_->busy;
auto ctx = std::make_shared<nf7::GenericContext>(*this, "initializing audio device");
data_->aq->Push(ctx, [d = data_, sel = selector_, cfg = cfg_](auto ma) mutable {
try {
if (!ma) {
throw nf7::Exception("audio task queue is broken");
}
if (d->dev) {
ma_device_uninit(&*d->dev);
d->dev = std::nullopt;
}
auto [devs, devs_n] = FetchDevs(ma, cfg.deviceType);
auto dinfo = std::visit(DeviceSelectorVisitor {devs, devs_n}, sel);
if (!dinfo) {
throw nf7::Exception("missing device");
}
cfg.playback.pDeviceID = cfg.capture.pDeviceID = &dinfo->id;
cfg.pUserData = d->ring.get();
cfg.dataCallback =
cfg.deviceType == ma_device_type_playback? kPlaybackCallback:
cfg.deviceType == ma_device_type_capture ? kCaptureCallback:
throw nf7::Exception("unknown mode");
d->dev.emplace();
if (MA_SUCCESS != ma_device_init(ma, &cfg, &*d->dev)) {
d->dev = std::nullopt;
throw nf7::Exception("failed to init audio device");
}
if (MA_SUCCESS != ma_device_start(&*d->dev)) {
ma_device_uninit(&*d->dev);
d->dev = std::nullopt;
throw nf7::Exception("failed to start device");
}
d->ring->Reset(cfg.sampleRate, GetChannels(cfg));
} catch (nf7::Exception& e) {
d->log.Error(e.msg());
}
--d->busy;
});
}
void IO::DeinitDev() noexcept {
if (!data_->aq) {
data_->log.Error("audio queue is missing");
return;
}
++data_->busy;
auto ctx = std::make_shared<nf7::GenericContext>(*this, "deleting audio device");
data_->aq->Push(ctx, [d = data_](auto) {
if (d->dev) {
ma_device_uninit(&*d->dev);
d->dev = std::nullopt;
}
--d->busy;
});
}
void IO::BuildNode() noexcept {
switch (cfg_.deviceType) {
case ma_device_type_playback:
nf7::Node::input_ = PlaybackLambda::kInputs;
nf7::Node::output_ = PlaybackLambda::kOutputs;
break;
case ma_device_type_capture:
nf7::Node::input_ = CaptureLambda::kInputs;
nf7::Node::output_ = CaptureLambda::kOutputs;
break;
default:
assert(false);
}
nf7::File::Touch();
}
void IO::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
data_->log.SetUp(*this);
try {
data_->aq =
ResolveUpwardOrThrow("_audio").
interfaceOrThrow<nf7::audio::Queue>().self();
InitDev();
BuildNode();
} catch (nf7::Exception&) {
data_->log.Info("audio context is not found");
}
return;
case Event::kRemove:
if (data_->aq) {
DeinitDev();
}
data_->aq = nullptr;
data_->log.TearDown();
return;
default:
return;
}
}
void IO::Update() noexcept {
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("ConfigPopup")) {
auto& p = config_popup_;
ImGui::TextUnformatted("Audio/Output");
if (ImGui::IsWindowAppearing()) {
if (!p) {
p = std::make_shared<ConfigPopup>();
}
p->cfg = cfg_;
p->selector = selector_;
if (data_->aq) {
p->FetchDevs(*this, data_->aq);
}
}
if (UpdateModeSelector(&p->cfg.deviceType)) {
if (data_->aq) {
p->FetchDevs(*this, data_->aq);
}
}
const ma_device_info* dev = nullptr;
if (!p->fetching) {
dev = UpdateDeviceSelector(&p->selector, p->devs, p->devs_n);
} else {
ImGui::LabelText("device", "fetching list...");
}
UpdatePresetSelector(&p->cfg, dev);
static const uint32_t kU32_1 = 1;
static const uint32_t kU32_16 = 16;
ImGui::DragScalar("sample rate", ImGuiDataType_U32, &p->cfg.sampleRate, 1, &kU32_1);
ImGui::DragScalar("channels", ImGuiDataType_U32, &GetChannels(p->cfg), 1, &kU32_1, &kU32_16);
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
cfg_ = p->cfg;
selector_ = p->selector;
InitDev();
BuildNode();
}
ImGui::EndPopup();
}
}
void IO::UpdateMenu() noexcept {
if (cfg_.deviceType == ma_device_type_playback) {
if (ImGui::MenuItem("simulate sinwave output for 1 sec")) {
const auto wave = GenerateSineWave(cfg_.sampleRate, cfg_.playback.channels);
data_->ring->Mix(wave.data(), wave.size(), 0);
}
ImGui::Separator();
}
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
}
void IO::UpdateTooltip() noexcept {
const char* mode =
cfg_.deviceType == ma_device_type_playback? "playback":
cfg_.deviceType == ma_device_type_capture ? "capture":
"unknown";
ImGui::Text("mode : %s", mode);
ImGui::Text("context : %s", data_->aq ? "ready": "broken");
ImGui::Text("device : %s", data_->busy? "initializing": data_->dev? "ready": "broken");
ImGui::Text("channels : %" PRIu32, cfg_.playback.channels);
ImGui::Text("sample rate: %" PRIu32, cfg_.sampleRate);
}
bool IO::UpdateModeSelector(ma_device_type* m) noexcept {
const char* mode =
*m == ma_device_type_playback? "playback":
*m == ma_device_type_capture? "capture":
"unknown";
bool ret = false;
if (ImGui::BeginCombo("mode", mode)) {
if (ImGui::Selectable("playback", *m == ma_device_type_playback)) {
*m = ma_device_type_playback;
ret = true;
}
if (ImGui::Selectable("capture", *m == ma_device_type_capture)) {
*m = ma_device_type_capture;
ret = true;
}
ImGui::EndCombo();
}
return ret;
}
const ma_device_info* IO::UpdateDeviceSelector(
DeviceSelector* sel, ma_device_info* devs, size_t n) noexcept {
const auto dev = std::visit(DeviceSelectorVisitor {devs, n}, *sel);
if (ImGui::BeginCombo("device", dev? dev->name: "(missing)")) {
for (size_t i = 0; i < n; ++i) {
const auto& d = devs[i];
const auto str = std::to_string(i)+": "+d.name;
if (ImGui::Selectable(str.c_str(), &d == dev)) {
if (std::holds_alternative<std::string>(*sel)) {
*sel = std::string {d.name};
} else if (std::holds_alternative<size_t>(*sel)) {
*sel = i;
} else {
assert(false);
}
}
}
ImGui::EndCombo();
}
bool b = std::holds_alternative<size_t>(*sel);
if (ImGui::Checkbox("remember device index", &b)) {
if (b) {
*sel = dev? static_cast<size_t>(dev - devs): size_t{0};
} else {
*sel = std::string {dev? dev->name: ""};
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("true : the device is remembered by its index\n"
"false: the device is remembered by its name");
}
return dev;
}
void IO::UpdatePresetSelector(ma_device_config* cfg, const ma_device_info* dev) noexcept {
auto& srate = cfg->sampleRate;
auto& ch = GetChannels(*cfg);
std::optional<size_t> match_idx = std::nullopt;
if (dev) {
for (size_t i = 0; i < dev->nativeDataFormatCount; ++i) {
const auto& fmt = dev->nativeDataFormats[i];
if (fmt.format != ma_format_f32) continue;
if (fmt.sampleRate == srate && fmt.channels == ch) {
match_idx = i;
break;
}
}
}
const auto preset_name = match_idx?
StringifyPreset(dev->nativeDataFormats[*match_idx]):
std::string {"(custom)"};
if (ImGui::BeginCombo("preset", preset_name.c_str())) {
if (dev) {
for (size_t i = 0; i < dev->nativeDataFormatCount; ++i) {
const auto& fmt = dev->nativeDataFormats[i];
if (fmt.format != ma_format_f32) continue;
const auto name = StringifyPreset(fmt);
if (ImGui::Selectable(name.c_str(), match_idx == i)) {
srate = fmt.sampleRate;
ch = fmt.channels;
}
}
}
ImGui::EndCombo();
}
}
}
} // namespace nf7

1453
file/gl_obj.cc Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
#include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/luajit.hh"
#include "common/luajit_queue.hh"
#include "common/ptr_selector.hh"
#include "common/queue.hh"
@@ -17,33 +18,36 @@
namespace nf7 {
namespace {
class LuaContext final : public nf7::File,
public nf7::DirItem {
class LuaContext final : public nf7::File, public nf7::DirItem {
public:
static inline const GenericTypeInfo<LuaContext> kType = {"LuaJIT/Context", {"DirItem",}};
static inline const nf7::GenericTypeInfo<nf7::LuaContext> kType = {
"LuaJIT/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives LuaJIT thread and task queue.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::luajit::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"create multiple contexts to execute LuaJIT paralelly");
ImGui::Bullet(); ImGui::TextUnformatted(
"the thread remains alive after file deletion until unused");
}
class Queue;
LuaContext(Env& env) noexcept :
File(kType, env), DirItem(DirItem::kTooltip) {
try {
q_ = std::make_shared<Queue>(env);
} catch (nf7::Exception&) {
// Thread construction failure (ignore it)
}
}
LuaContext(nf7::Env& env);
LuaContext(Env& env, Deserializer&) noexcept : LuaContext(env) {
LuaContext(nf7::Deserializer& ar) : LuaContext(ar.env()) {
}
void Serialize(Serializer&) const noexcept override {
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<LuaContext>(env);
}
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::luajit::Queue>(t).Select(this, q_.get());
}
@@ -56,22 +60,33 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
public std::enable_shared_from_this<LuaContext::Queue> {
public:
struct Runner final {
Runner(Queue& owner) noexcept : owner_(&owner) {
Runner(std::weak_ptr<Queue> owner) noexcept : owner_(owner) {
}
void operator()(Task&& t) {
t(owner_->L);
if (auto k = owner_.lock()) {
t(k->L);
}
}
private:
Queue* const owner_;
std::weak_ptr<Queue> owner_;
};
using Thread = nf7::Thread<Runner, Task>;
static std::shared_ptr<Queue> Create(LuaContext& f) {
auto ret = std::make_shared<Queue>(f);
ret->th_ = std::make_shared<Thread>(f, Runner {ret});
return ret;
}
Queue() = delete;
Queue(Env& env) :
L(luaL_newstate()),
env_(&env),
th_(std::make_shared<Thread>(env, Runner {*this})) {
if (!L) throw nf7::Exception("failed to create new Lua state");
Queue(LuaContext& f) : L(luaL_newstate()), env_(&f.env()) {
if (!L) {
throw nf7::Exception("failed to create new Lua state");
}
lua_pushthread(L);
nf7::luajit::PushImmEnv(L);
lua_setfenv(L, -2);
lua_pop(L, 1);
}
~Queue() noexcept {
th_->Push(
@@ -84,8 +99,8 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete;
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task));
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task, nf7::Env::Time t) noexcept override {
th_->Push(ctx, std::move(task), t);
}
std::shared_ptr<luajit::Queue> self() noexcept override { return shared_from_this(); }
@@ -96,7 +111,22 @@ class LuaContext::Queue final : public nf7::luajit::Queue,
Env* const env_;
std::shared_ptr<Thread> th_;
};
LuaContext::LuaContext(nf7::Env& env) :
nf7::File(kType, env),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip),
q_(Queue::Create(*this)) {
}
void LuaContext::UpdateMenu() noexcept {
if (ImGui::MenuItem("perform a full GC cycle")) {
q_->Push(
std::make_shared<nf7::GenericContext>(*this, "LuaJIT garbage collection"),
[](auto L) {
lua_gc(L, LUA_GCCOLLECT, 0);
}, nf7::Env::Time {});
}
}
void LuaContext::UpdateTooltip() noexcept {
ImGui::Text("tasks done: %zu", static_cast<size_t>(q_->tasksDone()));
if (q_) {

View File

@@ -1,34 +1,39 @@
#include <algorithm>
#include <exception>
#include <chrono>
#include <filesystem>
#include <memory>
#include <optional>
#include <typeinfo>
#include <variant>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_ref.hh"
#include "common/generic_context.hh"
#include "common/file_base.hh"
#include "common/generic_type_info.hh"
#include "common/generic_watcher.hh"
#include "common/gui_dnd.hh"
#include "common/lambda.hh"
#include "common/generic_memento.hh"
#include "common/gui_config.hh"
#include "common/gui_file.hh"
#include "common/gui_node.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/luajit_obj.hh"
#include "common/luajit_nfile_importer.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
#include "common/luajit_thread.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/task.hh"
#include "common/util_algorithm.hh"
using namespace std::literals;
@@ -37,437 +42,275 @@ using namespace std::literals;
namespace nf7 {
namespace {
class Node final : public nf7::File, public nf7::DirItem, public nf7::Node {
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
public:
static inline const GenericTypeInfo<Node> kType =
{"LuaJIT/Node", {"DirItem",}};
static inline const nf7::GenericTypeInfo<Node> kType =
{"LuaJIT/Node", {"nf7::DirItem", "nf7::Node"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Defines new pure Node without creating nfile.");
}
class FetchTask;
class Lambda;
Node(Env& env, File::Path&& path = {}, std::string_view desc = "",
std::vector<std::string>&& in = {},
std::vector<std::string>&& out = {}) noexcept :
File(kType, env),
DirItem(DirItem::kMenu | DirItem::kTooltip | DirItem::kDragDropTarget),
log_(std::make_shared<nf7::LoggerRef>()),
obj_(*this, std::move(path)), desc_(desc) {
input_ = std::move(in);
output_ = std::move(out);
struct Data {
Data() noexcept { }
std::string Stringify() const noexcept;
void Parse(const std::string&);
std::string script;
std::vector<std::string> inputs = {"in"};
std::vector<std::string> outputs = {"out"};
};
Node(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {}),
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
nf7::Node(nf7::Node::kCustomNode),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
mem_(std::move(data), *this),
importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) {
nf7::FileBase::Install(*log_);
mem_.onCommit = mem_.onRestore = [this]() {
cache_ = std::nullopt;
};
}
Node(Env& env, Deserializer& ar) : Node(env) {
ar(obj_, desc_, input_, output_);
for (auto itr = input_.begin(); itr < input_.end(); ++itr) {
if (std::find(itr+1, input_.end(), *itr) != input_.end()) {
throw nf7::DeserializeException("duplicated input socket");
}
}
for (auto itr = output_.begin(); itr < output_.end(); ++itr) {
if (std::find(itr+1, output_.end(), *itr) != output_.end()) {
throw nf7::DeserializeException("duplicated output socket");
}
}
Node(nf7::Deserializer& ar) : Node(ar.env()) {
ar(mem_->script, mem_->inputs, mem_->outputs);
nf7::util::Uniq(mem_->inputs);
nf7::util::Uniq(mem_->outputs);
}
void Serialize(Serializer& ar) const noexcept override {
ar(obj_, desc_, input_, output_);
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_->script, mem_->inputs, mem_->outputs);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Node>(
env, File::Path(obj_.path()), desc_,
std::vector<std::string>(input_), std::vector<std::string>(output_));
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Node>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Lambda> CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>&) noexcept override;
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
std::span<const std::string> GetInputs() const noexcept override {
return mem_->inputs;
}
std::span<const std::string> GetOutputs() const noexcept override {
return mem_->outputs;
}
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
void Handle(const Event&) noexcept override;
void Update() noexcept override;
static void UpdateList(std::vector<std::string>&) noexcept;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateDragDropTarget() noexcept override;
void UpdateNode(Node::Editor&) noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateWidget() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::Node>(t).Select(this);
return nf7::InterfaceSelector<
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::GenericWatcher> watcher_;
nf7::Life<Node> life_;
std::shared_ptr<nf7::luajit::Ref> handler_;
nf7::Task<std::shared_ptr<nf7::luajit::Ref>>::Holder fetch_;
const char* popup_ = nullptr;
// persistent params
nf7::FileRef obj_;
std::string desc_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> FetchHandler() noexcept;
void DropHandler() noexcept {
watcher_ = std::nullopt;
handler_ = nullptr;
fetch_ = {};
}
static void Join(std::string& str, const std::vector<std::string>& vec) noexcept {
str.clear();
for (const auto& name : vec) str += name + "\n";
}
static void Split(std::vector<std::string>& vec, const std::string& str) {
vec.clear();
for (size_t i = 0; i < str.size(); ++i) {
auto j = str.find('\n', i);
if (j == std::string::npos) j = str.size();
auto name = str.substr(i, j-i);
File::Path::ValidateTerm(name);
vec.push_back(std::move(name));
i = j;
}
}
};
class Node::FetchTask final : public nf7::Task<std::shared_ptr<nf7::luajit::Ref>> {
public:
FetchTask(Node& target) noexcept :
Task(target.env(), target.id()), target_(&target), log_(target_->log_) {
}
private:
Node* const target_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::GenericMemento<Data> mem_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro Proc() noexcept {
try {
auto& objf = *target_->obj_;
auto& obj = objf.interfaceOrThrow<nf7::luajit::Obj>();
auto handler = co_await obj.Build();
co_yield handler;
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
try {
*target_->obj_; // checks if objf is alive
target_->handler_ = handler;
auto& w = target_->watcher_;
w.emplace(env());
w->Watch(objf.id());
w->AddHandler(Event::kUpdate, [t = target_](auto&) {
if (t->handler_) {
t->log_->Info("detected update of handler object, drops cache");
t->handler_ = nullptr;
}
});
} catch (Exception& e) {
log_->Error("watcher setup failure: "+e.msg());
}
} catch (Exception& e) {
log_->Error("fetch failure: "+e.msg());
}
}
std::filesystem::file_time_type last_build_ = {};
std::shared_ptr<nf7::luajit::NFileImporter> importer_;
};
class Node::Lambda final : public nf7::Context, public nf7::Lambda,
class Node::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Node::Lambda> {
public:
Lambda(Node& f, const std::shared_ptr<Owner>& owner) noexcept :
Context(f), nf7::Lambda(owner),
file_(&f), file_id_(f.id()),
log_(f.log_), handler_(f.FetchHandler()) {
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
}
void Handle(size_t idx, nf7::Value&& v, const std::shared_ptr<nf7::Lambda>& caller) noexcept override {
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
auto self = shared_from_this();
handler_.ThenSub(self, [self, idx, v = std::move(v), caller](auto) mutable {
self->CallHandler({{idx, std::move(v)}}, caller);
});
}
void Abort() noexcept override {
for (auto& wth : th_) {
if (auto th = wth.lock()) {
th->Abort();
}
}
f_->Build().
ThenIf(self, [this, in](auto& func) mutable {
if (f_) StartThread(in, func);
}).
Catch<nf7::Exception>([log = log_](auto&) {
log->Warn("skips execution because of build failure");
});
} catch (nf7::ExpiredException&) {
}
private:
Node* const file_;
File::Id file_id_;
nf7::Life<Node>::Ref f_;
std::shared_ptr<nf7::LoggerRef> log_;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> handler_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
std::optional<nf7::luajit::Ref> ctxtable_;
std::mutex mtx_;
std::optional<nf7::luajit::Ref> ctx_;
using Param = std::pair<size_t, nf7::Value>;
void CallHandler(std::optional<Param>&& p, const std::shared_ptr<nf7::Lambda>& caller) noexcept
try {
void StartThread(const nf7::Node::Lambda::Msg& in,
const std::shared_ptr<nf7::luajit::Ref>& func) noexcept {
auto ljq = func->ljq();
auto self = shared_from_this();
th_.erase(
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
th_.end());
auto handler = handler_.value();
ljq_ = handler->ljq();
env().GetFileOrThrow(file_id_); // check if the owner is alive
auto th = std::make_shared<nf7::luajit::Thread>(
self, ljq_, owner(),
[self](auto& th, auto L) { self->HandleThread(th, L); });
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(in.sender, self);
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl));
th->Install(log_);
th_.emplace_back(th);
th->Install(f_->importer_);
ljq_->Push(self, [this, self, p = std::move(p), caller, handler, th](auto L) mutable {
auto thL = th->Init(L);
lua_rawgeti(thL, LUA_REGISTRYINDEX, handler->index());
if (p) {
lua_pushinteger(thL, static_cast<lua_Integer>(p->first));
nf7::luajit::PushValue(thL, p->second);
} else {
lua_pushnil(thL);
lua_pushnil(thL);
ljq->Push(self, [this, ljq, th, func, in](auto L) mutable {
{
std::unique_lock<std::mutex> k {mtx_};
if (!ctx_ || ctx_->ljq() != ljq) {
lua_createtable(L, 0, 0);
ctx_.emplace(shared_from_this(), ljq, L);
}
}
PushCaller(thL, caller);
PushContextTable(thL);
th->Resume(thL, 4);
L = th->Init(L);
func->PushSelf(L);
nf7::luajit::PushAll(L, in.name, in.value);
ctx_->PushSelf(L);
th->Resume(L, 3);
});
} catch (nf7::Exception& e) {
log_->Error("failed to call handler: "+e.msg());
}
void HandleThread(nf7::luajit::Thread& th, lua_State* L) noexcept {
switch (th.state()) {
case nf7::luajit::Thread::kFinished:
return;
case nf7::luajit::Thread::kPaused:
log_->Warn("unexpected yield");
ljq_->Push(shared_from_this(),
[th = th.shared_from_this(), L](auto) { th->Resume(L, 0); });
return;
default:
log_->Warn("luajit execution error: "s+lua_tostring(L, -1));
return;
}
}
void PushCaller(lua_State* L, const std::shared_ptr<nf7::Lambda>& caller) noexcept {
constexpr auto kTypeName = "nf7::File/LuaJIT/Node::Owner";
struct D final {
std::weak_ptr<nf7::Lambda> self;
std::shared_ptr<nf7::Lambda> caller;
};
new (lua_newuserdata(L, sizeof(D))) D { .self = weak_from_this(), .caller = caller };
if (luaL_newmetatable(L, kTypeName)) {
lua_pushcfunction(L, [](auto L) {
const auto& d = *reinterpret_cast<D*>(luaL_checkudata(L, 1, kTypeName));
const auto idx = luaL_checkint(L, 2);
auto self = d.self.lock();
if (!self) return luaL_error(L, "context expired");
if (idx < 0) return luaL_error(L, "negative index");
d.caller->Handle(static_cast<size_t>(idx), nf7::luajit::CheckValue(L, 3), self);
return 0;
});
lua_setfield(L, -2, "__call");
lua_pushcfunction(L, [](auto L) {
reinterpret_cast<D*>(luaL_checkudata(L, 1, kTypeName))->~D();
return 0;
});
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
}
void PushContextTable(lua_State* L) noexcept {
if (!ctxtable_) {
lua_createtable(L, 0, 0);
lua_pushvalue(L, -1);
const int idx = luaL_ref(L, LUA_REGISTRYINDEX);
ctxtable_.emplace(shared_from_this(), ljq_, idx);
} else {
lua_rawgeti(L, LUA_REGISTRYINDEX, ctxtable_->index());
}
}
};
std::shared_ptr<nf7::Lambda> Node::CreateLambda(
const std::shared_ptr<nf7::Lambda::Owner>& owner) noexcept {
return std::make_shared<Node::Lambda>(*this, owner);
}
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::FetchHandler() noexcept {
if (handler_) return handler_;
if (auto fetch = fetch_.lock()) return fetch->fu();
auto fetch = std::make_shared<FetchTask>(*this);
fetch->Start();
fetch_ = {fetch};
return fetch->fu();
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void Node::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
log_->SetUp(*this);
FetchHandler();
return;
case Event::kRemove:
log_->TearDown();
return;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::Build() noexcept
try {
if (cache_) return *cache_;
last_build_ = std::chrono::file_clock::now();
default:
return;
}
}
void Node::Update() noexcept {
const auto& style = ImGui::GetStyle();
const auto em = ImGui::GetFontSize();
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
if (const char* popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
auto ctx = std::make_shared<nf7::GenericContext>(*this, "lambda function builder");
auto ljq =
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string path;
static std::string desc;
static std::string in, out;
static std::vector<std::string> invec, outvec;
ImGui::TextUnformatted("LuaJIT/Node: config");
if (ImGui::IsWindowAppearing()) {
path = obj_.path().Stringify();
desc = desc_;
Join(in, input_);
Join(out, output_);
// create new thread
auto handler = luajit::Thread::CreatePromiseHandler<std::shared_ptr<luajit::Ref>>(pro, [ctx, ljq](auto L) {
if (lua_gettop(L) == 1 && lua_isfunction(L, 1)) {
return std::make_shared<nf7::luajit::Ref>(ctx, ljq, L);
} else {
throw nf7::Exception {"lambda script must return a function"};
}
});
auto th = std::make_shared<nf7::luajit::Thread>(ctx, ljq, std::move(handler));
th->Install(log_);
th->Install(importer_);
const auto w = ImGui::CalcItemWidth()/2 - style.ItemSpacing.x/2;
ImGui::InputText("path", &path);
ImGui::InputTextMultiline("description", &desc, {0, 4*em});
ImGui::BeginGroup();
ImGui::TextUnformatted("input:");
ImGui::InputTextMultiline("##input", &in, {w, 0});
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::TextUnformatted("output:");
ImGui::InputTextMultiline("##output", &out, {w, 0});
ImGui::EndGroup();
ImGui::SameLine(0, style.ItemInnerSpacing.x);
ImGui::TextUnformatted("sockets");
bool err = false;
File::Path p;
try {
p = File::Path::Parse(path);
ResolveOrThrow(p);
} catch (File::NotFoundException&) {
ImGui::Bullet(); ImGui::TextUnformatted("path seems to be missing");
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
err = true;
}
try {
Split(invec, in);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid inputs: %s", e.msg().c_str());
err = true;
}
try {
Split(outvec, out);
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::Text("invalid outputs: %s", e.msg().c_str());
err = true;
// start the thread
ljq->Push(ctx, [ctx, ljq, th, pro, script = mem_->script](auto L) mutable {
L = th->Init(L);
if (0 == luaL_loadstring(L, script.c_str())) {
th->Resume(L, 0);
} else {
pro.Throw<nf7::Exception>(lua_tostring(L, -1));
}
});
if (!err && ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
auto ctx = std::make_shared<nf7::GenericContext>(*this, "rebuilding node");
env().ExecMain(ctx, [&, p = std::move(p)]() mutable {
obj_ = std::move(p);
desc_ = std::move(desc);
input_ = std::move(invec);
output_ = std::move(outvec);
Touch();
cache_ = pro.future().
Catch<nf7::Exception>([log = log_](auto& e) {
log->Error(e);
});
}
return *cache_;
} catch (nf7::Exception&) {
return {std::current_exception()};
}
void Node::Update() noexcept {
nf7::FileBase::Update();
if (last_build_ < importer_->GetLatestMod()) {
cache_ = std::nullopt;
}
}
void Node::UpdateMenu() noexcept {
if (ImGui::BeginMenu("config")) {
nf7::gui::Config(mem_);
ImGui::EndMenu();
}
}
void Node::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("LuaJIT/Node");
ImGui::SameLine();
if (ImGui::SmallButton("config")) {
ImGui::OpenPopup("ConfigPopup");
}
if (ImGui::BeginPopup("ConfigPopup")) {
nf7::gui::Config(mem_);
ImGui::EndPopup();
}
}
void Node::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
ImGui::SameLine();
if (ImGui::SmallButton("build")) {
Build();
}
ImGui::Separator();
if (ImGui::MenuItem("try fetch handler")) {
FetchHandler();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("try to compile the script (for syntax check)");
}
if (ImGui::MenuItem("drop cached handler")) {
DropHandler();
}
}
void Node::UpdateTooltip() noexcept {
ImGui::Text("path : %s", obj_.path().Stringify().c_str());
ImGui::Text("handler: %s", handler_? "ready": "no");
ImGui::Spacing();
ImGui::Text("input:");
ImGui::Indent();
for (const auto& name : input_) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
nf7::gui::NodeInputSockets(mem_->inputs);
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
if (input_.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("output:");
ImGui::Indent();
for (const auto& name : output_) {
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
}
if (output_.empty()) {
ImGui::TextDisabled("(nothing)");
}
ImGui::Unindent();
ImGui::Text("description:");
ImGui::Indent();
if (desc_.empty()) {
ImGui::TextDisabled("(empty)");
} else {
ImGui::TextUnformatted(desc_.c_str());
}
ImGui::Unindent();
ImGui::TextDisabled("drop a file here to set it as source");
ImGui::SameLine();
nf7::gui::NodeOutputSockets(mem_->outputs);
}
void Node::UpdateDragDropTarget() noexcept {
if (auto p = gui::dnd::Accept<Path>(gui::dnd::kFilePath)) {
obj_ = std::move(*p);
}
void Node::UpdateWidget() noexcept {
nf7::gui::Config(mem_);
}
void Node::UpdateNode(Node::Editor&) noexcept {
std::string Node::Data::Stringify() const noexcept {
YAML::Emitter st;
st << YAML::BeginMap;
st << YAML::Key << "inputs";
st << YAML::Value << inputs;
st << YAML::Key << "outputs";
st << YAML::Value << outputs;
st << YAML::Key << "script";
st << YAML::Value << YAML::Literal << script;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Node::Data::Parse(const std::string& str)
try {
const auto yaml = YAML::Load(str);
auto new_inputs = yaml["inputs"] .as<std::vector<std::string>>();
auto new_outputs = yaml["outputs"].as<std::vector<std::string>>();
auto new_script = yaml["script"].as<std::string>();
if (nf7::util::Uniq(new_inputs) > 0) {
throw nf7::Exception {"duplicated inputs"};
}
if (nf7::util::Uniq(new_outputs) > 0) {
throw nf7::Exception {"duplicated outputs"};
}
inputs = std::move(new_inputs);
outputs = std::move(new_outputs);
script = std::move(new_script);
} catch (YAML::Exception& e) {
throw nf7::Exception {e.what()};
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

409
file/sequencer_adaptor.cc Normal file
View File

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

253
file/sequencer_call.cc Normal file
View File

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

1723
file/sequencer_timeline.cc Normal file

File diff suppressed because it is too large Load Diff

128
file/system_call.cc Normal file
View File

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

View File

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

197
file/system_event.cc Normal file
View File

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

82
file/system_imgui.cc Normal file
View File

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

View File

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

View File

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

View File

@@ -1,171 +0,0 @@
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <typeinfo>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include <yas/types/std/chrono.hpp>
#include <yas/types/std/string.hpp>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/async_buffer_adaptor.hh"
#include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/gui_window.hh"
#include "common/native_file.hh"
#include "common/ptr_selector.hh"
#include "common/queue.hh"
#include "common/yas_std_filesystem.hh"
namespace nf7 {
namespace {
class NativeFile final : public File,
public nf7::DirItem {
public:
static inline const GenericTypeInfo<NativeFile> kType = {
"System/NativeFile", {"AsyncBuffer", "DirItem"}};
NativeFile(Env& env, const std::filesystem::path& path = "", std::string_view mode = "") noexcept :
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
npath_(path), mode_(mode) {
}
NativeFile(Env& env, Deserializer& ar) : NativeFile(env) {
ar(npath_, mode_, lastmod_);
}
void Serialize(Serializer& ar) const noexcept override {
ar(npath_, mode_, lastmod_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<NativeFile>(env, npath_, mode_);
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void Handle(const Event& ev) noexcept override {
switch (ev.type) {
case Event::kAdd:
Reset();
return;
case Event::kRemove:
buf_ = nullptr;
return;
default:
return;
}
}
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::AsyncBuffer, nf7::DirItem>(t).Select(this, buf_.get());
}
private:
std::shared_ptr<nf7::AsyncBufferAdaptor> buf_;
const char* popup_ = nullptr;
// persistent params
std::filesystem::path npath_;
std::string mode_;
std::filesystem::file_time_type lastmod_;
void Reset() noexcept {
bool exlock = false;
nf7::Buffer::Flags flags = 0;
for (auto c : mode_) {
if (c == 'x') exlock = true;
flags |=
c == 'r'? nf7::Buffer::kRead:
c == 'w'? nf7::Buffer::kWrite: 0;
}
auto buf = std::make_shared<
nf7::NativeFile>(*this, env().npath()/npath_, flags, exlock);
buf_ = std::make_shared<nf7::AsyncBufferAdaptor>(buf, buf);
}
};
void NativeFile::Update() noexcept {
// file update check
try {
const auto lastmod = std::filesystem::last_write_time(env().npath()/npath_);
if (std::exchange(lastmod_, lastmod) < lastmod) {
Touch();
}
} catch (std::filesystem::filesystem_error&) {
}
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("ConfigPopup")) {
static std::string path;
static bool flag_exlock;
static bool flag_readable;
static bool flag_writeable;
ImGui::TextUnformatted("System/NativeFile: config");
if (ImGui::IsWindowAppearing()) {
path = npath_.generic_string();
flag_exlock = mode_.find('x') != std::string::npos;
flag_readable = mode_.find('r') != std::string::npos;
flag_writeable = mode_.find('w') != std::string::npos;
}
ImGui::InputText("path", &path);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"path to the native file system (base: '%s')",
env().npath().generic_string().c_str());
}
ImGui::Checkbox("exclusive lock", &flag_exlock);
ImGui::Checkbox("readable", &flag_readable);
ImGui::Checkbox("writeable", &flag_writeable);
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
npath_ = path;
mode_ = "";
if (flag_exlock) mode_ += 'x';
if (flag_readable) mode_ += 'r';
if (flag_writeable) mode_ += 'w';
auto ctx = std::make_shared<nf7::GenericContext>(*this, "resetting native file handle");
env().ExecMain(ctx, [this]() { Reset(); Touch(); });
}
if (!std::filesystem::exists(env().npath()/path)) {
ImGui::Bullet(); ImGui::TextUnformatted("target file seems to be missing...");
}
ImGui::EndPopup();
}
}
void NativeFile::UpdateMenu() noexcept {
if (ImGui::MenuItem("config")) {
popup_ = "ConfigPopup";
}
}
void NativeFile::UpdateTooltip() noexcept {
ImGui::Text("basepath: %s", env().npath().generic_string().c_str());
ImGui::Text("path : %s", npath_.generic_string().c_str());
ImGui::Text("mode : %s", mode_.c_str());
}
}
} // namespace nf7

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